From fd53891518c8e590f73639768d268b91e8949b57 Mon Sep 17 00:00:00 2001 From: dblock Date: Sat, 4 Aug 2018 12:57:09 -0400 Subject: [PATCH 001/290] Preparing for next developer iteration, 1.1.1. --- CHANGELOG.md | 11 ++++++++++- README.md | 3 ++- lib/grape/version.rb | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb52340e8..d4227788b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.1.1 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.1.0 (8/4/2018) #### Features @@ -6,7 +16,6 @@ #### Fixes - * [#1762](https://github.com/ruby-grape/grape/pull/1763): Fix unsafe HTML rendering on errors - [@ctennis](https://github.com/ctennis). * [#1759](https://github.com/ruby-grape/grape/pull/1759): Update appraisal for rails_edge - [@zvkemp](https://github.com/zvkemp). * [#1758](https://github.com/ruby-grape/grape/pull/1758): Fix expanding load_path in gemspec - [@2maz](https://github.com/2maz). diff --git a/README.md b/README.md index 4cc8f6c45..876e1a4d2 100644 --- a/README.md +++ b/README.md @@ -145,8 +145,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, **1.1.0**. +You're reading the documentation for the next release of Grape, which should be **1.1.1**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.1.0](https://github.com/ruby-grape/grape/blob/v1.1.0/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 47a2dda13..6608850f6 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.1.0'.freeze + VERSION = '1.1.1'.freeze end From 798b3858b0c3f400f873d0b56e48e0ac176f97b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Glauco=20Cust=C3=B3dio?= Date: Tue, 14 Aug 2018 09:44:56 -0300 Subject: [PATCH 002/290] Fix example The class method `decode` does not exist for `Base64`. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 876e1a4d2..172e1007e 100644 --- a/README.md +++ b/README.md @@ -935,7 +935,7 @@ parameter, and the return value must match the given `type`. ```ruby params do - requires :passwd, type: String, coerce_with: Base64.method(:decode) + requires :passwd, type: String, coerce_with: Base64.method(:decode64) requires :loud_color, type: Color, coerce_with: ->(c) { Color.parse(c.downcase) } requires :obj, type: Hash, coerce_with: JSON do From c117bff7d22971675f4b34367d3a98bc31c8fc02 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Thu, 30 Aug 2018 15:20:38 +0200 Subject: [PATCH 003/290] Validate response from the exception handler. Closes #1757. --- CHANGELOG.md | 1 + README.md | 6 +++--- UPGRADING.md | 19 +++++++++++++++++++ lib/grape.rb | 1 + lib/grape/exceptions/invalid_response.rb | 9 +++++++++ lib/grape/locale/en.yml | 1 + lib/grape/middleware/error.rb | 10 ++++++++-- spec/grape/api_spec.rb | 10 ++++++++++ .../grape/exceptions/invalid_response_spec.rb | 11 +++++++++++ spec/grape/middleware/exception_spec.rb | 2 +- 10 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 lib/grape/exceptions/invalid_response.rb create mode 100644 spec/grape/exceptions/invalid_response_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index d4227788b..f3e9b5ff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469). ### 1.1.0 (8/4/2018) diff --git a/README.md b/README.md index 172e1007e..5514be7a8 100644 --- a/README.md +++ b/README.md @@ -2183,7 +2183,7 @@ You can also rescue all exceptions with a code block and handle the Rack respons ```ruby class Twitter::API < Grape::API rescue_from :all do |e| - Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish + Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }) end end ``` @@ -2254,9 +2254,9 @@ class Twitter::API < Grape::API end ``` -The `rescue_from` block must return a `Rack::Response` object, call `error!` or re-raise an exception. +The `rescue_from` handler must return a `Rack::Response` object, call `error!`, or raise an exception (either the original exception or another custom one). The exception raised in `rescue_from` will be handled outside Grape. For example, if you mount Grape in Rails, the exception will be handle by [Rails Action Controller](https://guides.rubyonrails.org/action_controller_overview.html#rescue). -The `with` keyword is available as `rescue_from` options, it can be passed method name or Proc object. +Alternately, use the `with` option in `rescue_from` to specify a method or a `proc`. ```ruby class Twitter::API < Grape::API diff --git a/UPGRADING.md b/UPGRADING.md index d63fa23e6..6edbf2ad1 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,25 @@ Upgrading Grape =============== +### Upgrading to >= 1.1.1 + +#### Changes in rescue_from returned object + +Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation. + +```ruby +class Twitter::API < Grape::API + rescue_from :all do |e| + # version prior to 1.1.1 + Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish + # 1.1.1 version + Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }) + end +end +``` + +See [#1757](https://github.com/ruby-grape/grape/pull/1757) and [#1776](https://github.com/ruby-grape/grape/pull/1776) for more information. + ### Upgrading to >= 1.1.0 #### Changes in HTTP Response Code for Unsupported Content Type diff --git a/lib/grape.rb b/lib/grape.rb index 0ee168a6d..d626ec5c0 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -75,6 +75,7 @@ module Exceptions autoload :InvalidAcceptHeader autoload :InvalidVersionHeader autoload :MethodNotAllowed + autoload :InvalidResponse end module Extensions diff --git a/lib/grape/exceptions/invalid_response.rb b/lib/grape/exceptions/invalid_response.rb new file mode 100644 index 000000000..3b92ab9cc --- /dev/null +++ b/lib/grape/exceptions/invalid_response.rb @@ -0,0 +1,9 @@ +module Grape + module Exceptions + class InvalidResponse < Base + def initialize + super(message: compose_message(:invalid_response)) + end + end + end +end diff --git a/lib/grape/locale/en.yml b/lib/grape/locale/en.yml index b78de1ed4..7ebfa97fc 100644 --- a/lib/grape/locale/en.yml +++ b/lib/grape/locale/en.yml @@ -49,4 +49,5 @@ en: invalid_version_header: problem: 'Invalid version header' resolution: '%{message}' + invalid_response: 'Invalid response' diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 0991b4884..05ea790ce 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -73,7 +73,7 @@ def rack_response(message, status = options[:default_status], headers = { Grape: if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML message = ERB::Util.html_escape(message) end - Rack::Response.new([message], status, headers).finish + Rack::Response.new([message], status, headers) end def format_message(message, backtrace, original_exception = nil) @@ -127,7 +127,13 @@ def run_rescue_handler(handler, error) handler = public_method(handler) end - handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler) + response = handler.arity.zero? ? instance_exec(&handler) : instance_exec(error, &handler) + + if response.is_a?(Rack::Response) + response + else + run_rescue_handler(:default_rescue_handler, Grape::Exceptions::InvalidResponse.new) + end end end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index c38205f47..e54e163ab 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1723,6 +1723,16 @@ class CustomError < Grape::Exceptions::Base; end expect(last_response.status).to eql 500 expect(last_response.body).to eq('Formatter Error') end + + it 'uses default_rescue_handler to handle invalid response from rescue_from' do + subject.rescue_from(:all) { 'error' } + subject.get('/') { raise } + + expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original + get '/' + expect(last_response.status).to eql 500 + expect(last_response.body).to eql 'Invalid response' + end end describe '.rescue_from klass, block' do diff --git a/spec/grape/exceptions/invalid_response_spec.rb b/spec/grape/exceptions/invalid_response_spec.rb new file mode 100644 index 000000000..4603ca13b --- /dev/null +++ b/spec/grape/exceptions/invalid_response_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Grape::Exceptions::InvalidResponse do + describe '#message' do + let(:error) { described_class.new } + + it 'contains the problem in the message' do + expect(error.message).to include('Invalid response') + end + end +end diff --git a/spec/grape/middleware/exception_spec.rb b/spec/grape/middleware/exception_spec.rb index c11b49649..608e1f5da 100644 --- a/spec/grape/middleware/exception_spec.rb +++ b/spec/grape/middleware/exception_spec.rb @@ -128,7 +128,7 @@ def app subject do Rack::Builder.app do use Spec::Support::EndpointFaker - use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { [200, {}, 'rescued'] } } + use Grape::Middleware::Error, rescue_handlers: { NotImplementedError => -> { Rack::Response.new('rescued', 200, {}) } } run ExceptionSpec::OtherExceptionApp end end From 6ff7862433a52cd27b4884ed9c02e4f19c263e5c Mon Sep 17 00:00:00 2001 From: Seth Herr Date: Sun, 2 Sep 2018 10:28:32 -0700 Subject: [PATCH 004/290] Add note about using Doorkeeper for OAuth2 support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5514be7a8..163e883c4 100644 --- a/README.md +++ b/README.md @@ -2894,7 +2894,7 @@ end ``` -Use [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support. +Use [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support. ## Describing and Inspecting an API From f996a09a95a2078b0dbebffc59932911d223beb9 Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 2 Sep 2018 20:10:27 +0200 Subject: [PATCH 005/290] Fix: README TOC. --- README.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 5514be7a8..f00f19972 100644 --- a/README.md +++ b/README.md @@ -38,35 +38,35 @@ - [Integer/Fixnum and Coercions](#integerfixnum-and-coercions) - [Custom Types and Coercions](#custom-types-and-coercions) - [Multipart File Parameters](#multipart-file-parameters) - - [First-Class `JSON` Types](#first-class-json-types) + - [First-Class JSON Types](#first-class-json-types) - [Multiple Allowed Types](#multiple-allowed-types) - [Validation of Nested Parameters](#validation-of-nested-parameters) - [Dependent Parameters](#dependent-parameters) - [Group Options](#group-options) - [Alias](#alias) - [Built-in Validators](#built-in-validators) - - [`allow_blank`](#allow_blank) - - [`values`](#values) - - [`except_values`](#except_values) - - [`regexp`](#regexp) - - [`mutually_exclusive`](#mutually_exclusive) - - [`exactly_one_of`](#exactly_one_of) - - [`at_least_one_of`](#at_least_one_of) - - [`all_or_none_of`](#all_or_none_of) - - [Nested `mutually_exclusive`, `exactly_one_of`, `at_least_one_of`, `all_or_none_of`](#nested-mutually_exclusive-exactly_one_of-at_least_one_of-all_or_none_of) + - [allow_blank](#allow_blank) + - [values](#values) + - [except_values](#except_values) + - [regexp](#regexp) + - [mutually_exclusive](#mutually_exclusive) + - [exactly_one_of](#exactly_one_of) + - [at_least_one_of](#at_least_one_of) + - [all_or_none_of](#all_or_none_of) + - [Nested mutually_exclusive, exactly_one_of, at_least_one_of, all_or_none_of](#nested-mutually_exclusive-exactly_one_of-at_least_one_of-all_or_none_of) - [Namespace Validation and Coercion](#namespace-validation-and-coercion) - [Custom Validators](#custom-validators) - [Validation Errors](#validation-errors) - [I18n](#i18n) - [Custom Validation messages](#custom-validation-messages) - - [`presence`, `allow_blank`, `values`, `regexp`](#presence-allow_blank-values-regexp) - - [`all_or_none_of`](#all_or_none_of-1) - - [`mutually_exclusive`](#mutually_exclusive-1) - - [`exactly_one_of`](#exactly_one_of-1) - - [`at_least_one_of`](#at_least_one_of-1) - - [`Coerce`](#coerce) - - [`With Lambdas`](#with-lambdas) - - [`Pass symbols for i18n translations`](#pass-symbols-for-i18n-translations) + - [presence, allow_blank, values, regexp](#presence-allow_blank-values-regexp) + - [all_or_none_of](#all_or_none_of-1) + - [mutually_exclusive](#mutually_exclusive-1) + - [exactly_one_of](#exactly_one_of-1) + - [at_least_one_of](#at_least_one_of-1) + - [Coerce](#coerce) + - [With Lambdas](#with-lambdas) + - [Pass symbols for i18n translations](#pass-symbols-for-i18n-translations) - [Overriding Attribute Names](#overriding-attribute-names) - [With Default](#with-default) - [Headers](#headers) From 5b20f2ab9196f5987982094bc3db1d6ac19af08f Mon Sep 17 00:00:00 2001 From: Michael Canden-Lennox Date: Tue, 4 Sep 2018 15:49:41 +0100 Subject: [PATCH 006/290] Add integration test to show missing documented DSL method (`.insert`) It is documented in the README for this project that there is a DSL method (`.insert`) that can be utilised but this is not the case. The notable passage is: ``` You can add your custom middleware with use, that push the middleware onto the stack, and you can also control where the middleware is inserted using insert, insert_before and insert_after. ``` The notable example of this is: ``` class CustomOverwriter < Grape::Middleware::Base def after [200, { 'Content-Type' => 'text/plain' }, [@options[:message]]] end end class API < Grape::API use Overwriter insert_before Overwriter, CustomOverwriter, message: 'Overwritten again.' insert 0, CustomOverwriter, message: 'Overwrites all other middleware.' get '/' do end end ``` This method is not actually available though and when invoked will raise a NoMethodError. This appears to have just been an error of omission in the original commit adding the middleware stack and extending the DSL to include the ability to interact with it in a more granular fashion (https://github.com/ruby-grape/grape/commit/c2e41f72b344f478abdfdc780461963a994838d3). This commit adds a failing test case which attempts to utilise this documented feature. --- spec/grape/api_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index e54e163ab..df58f88e0 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1348,6 +1348,28 @@ def call(env) end end + describe '.insert' do + it 'inserts middleware in a specific location in the stack' do + m = Class.new(Grape::Middleware::Base) do + def call(env) + env['phony.args'] ||= [] + env['phony.args'] << @options[:message] + @app.call(env) + end + end + + subject.use ApiSpec::PhonyMiddleware, 'bye' + subject.insert 0, m, message: 'good' + subject.insert 0, m, message: 'hello' + subject.get '/' do + env['phony.args'].join(' ') + end + + get '/' + expect(last_response.body).to eql 'hello good bye' + end + end + describe '.http_basic' do it 'protects any resources on the same scope' do subject.http_basic do |u, _p| From a4bd24acabd8ee8f4ea012fe4042e42cd2181021 Mon Sep 17 00:00:00 2001 From: Michael Canden-Lennox Date: Tue, 4 Sep 2018 15:54:45 +0100 Subject: [PATCH 007/290] Allow a user to insert a middleware at a specific location in the stack. This commit adds a documented but not implemented feature to allow a user to insert a middleware at a specific point in the stack. It does this by extending the Grape::DSL::Middleware class with a new method `.insert` which delegates down to the already existing methods in existance on the Grape::Middleware::Stack class. --- lib/grape/dsl/middleware.rb | 7 +++++++ spec/grape/dsl/middleware_spec.rb | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/grape/dsl/middleware.rb b/lib/grape/dsl/middleware.rb index 7026f8995..bd30c7498 100644 --- a/lib/grape/dsl/middleware.rb +++ b/lib/grape/dsl/middleware.rb @@ -21,6 +21,13 @@ def use(middleware_class, *args, &block) namespace_stackable(:middleware, arr) end + def insert(*args, &block) + arr = [:insert, *args] + arr << block if block_given? + + namespace_stackable(:middleware, arr) + end + def insert_before(*args, &block) arr = [:insert_before, *args] arr << block if block_given? diff --git a/spec/grape/dsl/middleware_spec.rb b/spec/grape/dsl/middleware_spec.rb index b9bf96094..bacb18b00 100644 --- a/spec/grape/dsl/middleware_spec.rb +++ b/spec/grape/dsl/middleware_spec.rb @@ -22,6 +22,14 @@ class Dummy end end + describe '.insert' do + it 'adds a middleware with the right operation' do + expect(subject).to receive(:namespace_stackable).with(:middleware, [:insert, 0, :arg1, proc]) + + subject.insert 0, :arg1, &proc + end + end + describe '.insert_before' do it 'adds a middleware with the right operation' do expect(subject).to receive(:namespace_stackable).with(:middleware, [:insert_before, foo_middleware, :arg1, proc]) From 435ff14899c8a1dc34dc0c5cb0cd643e8f1e6b38 Mon Sep 17 00:00:00 2001 From: Michael Canden-Lennox Date: Tue, 4 Sep 2018 16:23:13 +0100 Subject: [PATCH 008/290] Add CHANGELOG entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3e9b5ff8..4910c574f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Your contribution here. * [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469). +* [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox). ### 1.1.0 (8/4/2018) From f68a385d8f5f1e838d1a9b3fd049a45582d01cce Mon Sep 17 00:00:00 2001 From: Darrell Nash Date: Thu, 23 Aug 2018 04:07:32 -0700 Subject: [PATCH 009/290] Added spec to demonstrate bug with nested requirements. --- .../api/routes_with_requirements_spec.rb | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 spec/grape/api/routes_with_requirements_spec.rb diff --git a/spec/grape/api/routes_with_requirements_spec.rb b/spec/grape/api/routes_with_requirements_spec.rb new file mode 100644 index 000000000..e66963c38 --- /dev/null +++ b/spec/grape/api/routes_with_requirements_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Grape::Endpoint do + subject { Class.new(Grape::API) } + + def app + subject + end + + context 'get' do + it 'routes to a namespace param with dots' do + subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do + get '/' do + params[:ns_with_dots] + end + end + + get '/test.id.with.dots' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq 'test.id.with.dots' + end + + it 'routes to a path with multiple params with dots' do + subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^\/]+}, + another_id_with_dots: %r{[^\/]+} } do + "#{params[:id_with_dots]}/#{params[:another_id_with_dots]}" + end + + get '/test.id/test2.id' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq 'test.id/test2.id' + end + + it 'routes to namespace and path params with dots, with overridden requirements' do + subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do + get ':another_id_with_dots', requirements: { ns_with_dots: %r{[^\/]+}, + another_id_with_dots: %r{[^\/]+} } do + "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}" + end + end + + get '/test.id/test2.id' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq 'test.id/test2.id' + end + + it 'routes to namespace and path params with dots, with merged requirements' do + subject.namespace ':ns_with_dots', requirements: { ns_with_dots: %r{[^\/]+} } do + get ':another_id_with_dots', requirements: { another_id_with_dots: %r{[^\/]+} } do + "#{params[:ns_with_dots]}/#{params[:another_id_with_dots]}" + end + end + + get '/test.id/test2.id' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq 'test.id/test2.id' + end + end +end From c701d4887defe6ac5ad1736428a850e0e029bfa9 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Thu, 6 Sep 2018 21:16:01 +0800 Subject: [PATCH 010/290] Fix route requirements bug This was a bug introduced by commit 9f4ba67. The commit replaces `options[:route_options].clone.merge(...)` with `options[:route_options].clone.reverse_merge(...)`. That causes disappear of the requirements in namespace. --- CHANGELOG.md | 1 + lib/grape/endpoint.rb | 2 +- spec/grape/api/routes_with_requirements_spec.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4910c574f..f776bf878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Your contribution here. * [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469). * [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox). +* [#1788](https://github.com/ruby-grape/grape/pull/1788): Fix route requirements bug - [@darren987469](https://github.com/darren987469), [@darrellnash](https://github.com/darrellnash). ### 1.1.0 (8/4/2018) diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index aacf0472a..5100f9654 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -200,7 +200,7 @@ def prepare_version end def merge_route_options(**default) - options[:route_options].clone.reverse_merge(**default) + options[:route_options].clone.merge(**default) end def map_routes diff --git a/spec/grape/api/routes_with_requirements_spec.rb b/spec/grape/api/routes_with_requirements_spec.rb index e66963c38..c6ce2ccee 100644 --- a/spec/grape/api/routes_with_requirements_spec.rb +++ b/spec/grape/api/routes_with_requirements_spec.rb @@ -22,7 +22,7 @@ def app it 'routes to a path with multiple params with dots' do subject.get ':id_with_dots/:another_id_with_dots', requirements: { id_with_dots: %r{[^\/]+}, - another_id_with_dots: %r{[^\/]+} } do + another_id_with_dots: %r{[^\/]+} } do "#{params[:id_with_dots]}/#{params[:another_id_with_dots]}" end From 35b432ca51fed01e03337d53a68001faa9e63f2d Mon Sep 17 00:00:00 2001 From: Darren Date: Mon, 17 Sep 2018 05:53:45 +0800 Subject: [PATCH 011/290] Support more options in desc block (#1791) * Support more options in desc blockFix #1789.Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`,`produces`, `consumes`, `tags` options in desc block. * Fix rubocop and update CHANGELOG * Refine documentation [ci skip] Add real mime type in README. Add options documentation in `desc`. --- CHANGELOG.md | 1 + README.md | 16 ++++++++++++++-- lib/grape/dsl/desc.rb | 18 +++++++++++++++++- spec/grape/dsl/desc_spec.rb | 18 +++++++++++++++++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f776bf878..ef2b8ee1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469). #### Fixes diff --git a/README.md b/README.md index e3ed789f7..94e80896d 100644 --- a/README.md +++ b/README.md @@ -446,10 +446,13 @@ version 'v1', using: :param, parameter: 'v' ## Describing Methods -You can add a description to API methods and namespaces. +You can add a description to API methods and namespaces. The description would be used by [grape-swagger][grape-swagger] to generate swagger compliant documentation. + +Note: Description block is only for documentation and won't affects API behavior. ```ruby desc 'Returns your public timeline.' do + summary 'summary' detail 'more details' params API::Entities::Status.documentation success API::Entities::Entity @@ -463,7 +466,13 @@ desc 'Returns your public timeline.' do description: 'Not really needed', required: false } - + hidden false + deprecated false + is_array true + nickname 'nickname' + produces ['application/json'] + consumes ['application/json'] + tags ['tag1', 'tag2'] end get :public_timeline do Status.limit(20) @@ -476,6 +485,9 @@ end * `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities * `named`: A helper to give a route a name and find it with this name in the documentation Hash * `headers`: A definition of the used Headers +* Other options can be found in [grape-swagger][grape-swagger] + +[grape-swagger]: https://github.com/ruby-grape/grape-swagger ## Parameters diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index e758bf2c9..1fe128127 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -4,6 +4,7 @@ module Desc include Grape::DSL::Settings # Add a description to the next namespace or function. + # @option options :summary [String] summary for this endpoint # @param description [String] descriptive string for this endpoint # or namespace # @param options [Hash] other properties you can set to describe the @@ -17,6 +18,13 @@ module Desc # endpoint may return, with their meanings, in a 2d array # @option options :named [String] a specific name to help find this route # @option options :headers [Hash] HTTP headers this method can accept + # @option options :hidden [Boolean] hide the endpoint or not + # @option options :deprecated [Boolean] deprecate the endpoint or not + # @option options :is_array [Boolean] response entity is array or not + # @option options :nickname [String] nickname of the endpoint + # @option options :produces [Array[String]] a list of MIME types the endpoint produce + # @option options :consumes [Array[String]] a list of MIME types the endpoint consume + # @option options :tags [Array[String]] a list of tags # @yield a block yielding an instance context with methods mapping to # each of the above, except that :entity is also aliased as #success # and :http_codes is aliased as #failure. @@ -78,13 +86,21 @@ def unset_description_field(field) def desc_container Module.new do include Grape::Util::StrictHashConfiguration.module( + :summary, :description, :detail, :params, :entity, :http_codes, :named, - :headers + :headers, + :hidden, + :deprecated, + :is_array, + :nickname, + :produces, + :consumes, + :tags ) def config_context.success(*args) diff --git a/spec/grape/dsl/desc_spec.rb b/spec/grape/dsl/desc_spec.rb index a7ff7da33..bd9f61c5f 100644 --- a/spec/grape/dsl/desc_spec.rb +++ b/spec/grape/dsl/desc_spec.rb @@ -21,6 +21,7 @@ class Dummy it 'can be set with a block' do expected_options = { + summary: 'summary', description: 'The description', detail: 'more details', params: { first: :param }, @@ -34,10 +35,18 @@ class Dummy XOptionalHeader: { description: 'Not really needed', required: false - }] + }], + hidden: false, + deprecated: false, + is_array: true, + nickname: 'nickname', + produces: %w[array of mime_types], + consumes: %w[array of mime_types], + tags: %w[tag1 tag2] } subject.desc 'The description' do + summary 'summary' detail 'more details' params(first: :param) success Object @@ -51,6 +60,13 @@ class Dummy description: 'Not really needed', required: false }] + hidden false + deprecated false + is_array true + nickname 'nickname' + produces %w[array of mime_types] + consumes %w[array of mime_types] + tags %w[tag1 tag2] end expect(subject.namespace_setting(:description)).to eq(expected_options) From 9369f5f87d3f790223e29e6689e24f3baadc7b5d Mon Sep 17 00:00:00 2001 From: Laura Eck Date: Sun, 23 Sep 2018 14:13:45 +0900 Subject: [PATCH 012/290] Add spec to confirm that requires validates correctly with deep nested params --- spec/grape/validations_spec.rb | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index a5b79be9e..304b8cd87 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -120,6 +120,62 @@ def define_optional_using end end + context 'requires with nested params' do + before do + subject.params do + requires :first_level, type: Hash do + optional :second_level, type: Array do + requires :value, type: Integer + optional :name, type: String + optional :third_level, type: Array do + requires :value, type: Integer + optional :name, type: String + optional :fourth_level, type: Array do + requires :value, type: Integer + optional :name, type: String + end + end + end + end + end + subject.put('/required') { 'required works' } + end + + let(:request_params) do + { + first_level: { + second_level: [ + { value: 1, name: 'Lisa' }, + { + value: 2, + name: 'James', + third_level: [ + { value: 'three', name: 'Sophie' }, + { + value: 4, + name: 'Jenny', + fourth_level: [ + { name: 'Samuel' }, { value: 6, name: 'Jane' } + ] + } + ] + } + ] + } + } + end + + it 'validates correctly in deep nested params' do + put '/required', request_params.to_json, 'CONTENT_TYPE' => 'application/json' + + expect(last_response.status).to eq(400) + expect(last_response.body).to eq( + 'first_level[second_level][1][third_level][0][value] is invalid, ' \ + 'first_level[second_level][1][third_level][1][fourth_level][0][value] is missing' + ) + end + end + context 'requires :all using Grape::Entity documentation' do def define_requires_all documentation = { From ed0e2210dc3179545b4412632061248f7b1ba083 Mon Sep 17 00:00:00 2001 From: Ben Schmeckpeper Date: Tue, 2 Oct 2018 08:54:35 -0500 Subject: [PATCH 013/290] Handle improper Accept headers (#1795) --- CHANGELOG.md | 1 + lib/grape/middleware/versioner/header.rb | 4 ++-- spec/grape/middleware/versioner/header_spec.rb | 6 ++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef2b8ee1e..9312324e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck). * [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469). #### Fixes diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index e07cacb2a..b2cd806df 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -173,7 +173,7 @@ def error_headers # @return [Boolean] whether the content type sets a vendor def vendor?(media_type) _, subtype = Rack::Accept::Header.parse_media_type(media_type) - subtype[HAS_VENDOR_REGEX] + subtype.present? && subtype[HAS_VENDOR_REGEX] end def request_vendor(media_type) @@ -190,7 +190,7 @@ def request_version(media_type) # @return [Boolean] whether the content type sets an API version def version?(media_type) _, subtype = Rack::Accept::Header.parse_media_type(media_type) - subtype[HAS_VERSION_REGEX] + subtype.present? && subtype[HAS_VERSION_REGEX] end end end diff --git a/spec/grape/middleware/versioner/header_spec.rb b/spec/grape/middleware/versioner/header_spec.rb index 5befcc6a7..aaedc757d 100644 --- a/spec/grape/middleware/versioner/header_spec.rb +++ b/spec/grape/middleware/versioner/header_spec.rb @@ -160,6 +160,12 @@ expect(subject.call({}).first).to eq(200) end + it 'succeeds if :strict is set to false and given an invalid header' do + @options[:version_options][:strict] = false + expect(subject.call('HTTP_ACCEPT' => 'yaml').first).to eq(200) + expect(subject.call({}).first).to eq(200) + end + context 'when :strict is set' do before do @options[:versions] = ['v1'] From f0d1fa1ecdc6bb81bc9f3898c7b57b2031bec6c0 Mon Sep 17 00:00:00 2001 From: Laura Eck Date: Sat, 29 Sep 2018 10:12:50 +0900 Subject: [PATCH 014/290] Fix crash when available locales are enforced but fallback locale unavailable (#1796) --- CHANGELOG.md | 1 + README.md | 2 + lib/grape/exceptions/base.rb | 10 ++++- spec/grape/exceptions/base_spec.rb | 61 ++++++++++++++++++++++++++++++ spec/spec_helper.rb | 4 +- 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 spec/grape/exceptions/base_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 9312324e6..70b91a4cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Fixes * Your contribution here. +* [#1796](https://github.com/ruby-grape/grape/pull/1796): Fix crash when available locales are enforced but fallback locale unavailable - [@Morred](https://github.com/Morred). * [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469). * [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox). * [#1788](https://github.com/ruby-grape/grape/pull/1788): Fix route requirements bug - [@darren987469](https://github.com/darren987469), [@darrellnash](https://github.com/darrellnash). diff --git a/README.md b/README.md index 94e80896d..20e6bf11c 100644 --- a/README.md +++ b/README.md @@ -1559,6 +1559,8 @@ end Grape supports I18n for parameter-related error messages, but will fallback to English if translations for the default locale have not been provided. See [en.yml](lib/grape/locale/en.yml) for message keys. +In case your app enforces available locales only and :en is not included in your available locales, Grape cannot fall back to English and will return the translation key for the error message. To avoid this behaviour, either provide a translation for your default locale or add :en to your available locales. + ### Custom Validation messages Grape supports custom validation messages for parameter-related and coerce-related error messages. diff --git a/lib/grape/exceptions/base.rb b/lib/grape/exceptions/base.rb index d54fe6efe..82ddbae69 100644 --- a/lib/grape/exceptions/base.rb +++ b/lib/grape/exceptions/base.rb @@ -74,7 +74,15 @@ def translate(key, **options) options = options.dup options[:default] &&= options[:default].to_s message = ::I18n.translate(key, **options) - message.present? ? message : ::I18n.translate(key, locale: FALLBACK_LOCALE, **options) + message.present? ? message : fallback_message(key, **options) + end + + def fallback_message(key, **options) + if ::I18n.enforce_available_locales && !::I18n.available_locales.include?(FALLBACK_LOCALE) + key + else + ::I18n.translate(key, locale: FALLBACK_LOCALE, **options) + end end end end diff --git a/spec/grape/exceptions/base_spec.rb b/spec/grape/exceptions/base_spec.rb new file mode 100644 index 000000000..e793740f3 --- /dev/null +++ b/spec/grape/exceptions/base_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe Grape::Exceptions::Base do + describe '#compose_message' do + subject { described_class.new.send(:compose_message, key, attributes) } + + let(:key) { :invalid_formatter } + let(:attributes) { { klass: String, to_format: 'xml' } } + + after do + I18n.available_locales = %i[en] + I18n.default_locale = :en + end + + context 'when I18n enforces available locales' do + before { I18n.enforce_available_locales = true } + + context 'when the fallback locale is available' do + before do + I18n.available_locales = %i[de en] + I18n.default_locale = :de + end + + it 'returns the translated message' do + expect(subject).to eq('cannot convert String to xml') + end + end + + context 'when the fallback locale is not available' do + before do + I18n.available_locales = %i[de jp] + I18n.default_locale = :de + end + + it 'returns the translation string' do + expect(subject).to eq("grape.errors.messages.#{key}") + end + end + end + + context 'when I18n does not enforce available locales' do + before { I18n.enforce_available_locales = false } + + context 'when the fallback locale is available' do + before { I18n.available_locales = %i[de en] } + + it 'returns the translated message' do + expect(subject).to eq('cannot convert String to xml') + end + end + + context 'when the fallback locale is not available' do + before { I18n.available_locales = %i[de jp] } + + it 'returns the translated message' do + expect(subject).to eq('cannot convert String to xml') + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e00fba228..9baf9bf57 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,7 +12,9 @@ require file end -I18n.enforce_available_locales = false +# The default value for this setting is true in a standard Rails app, +# so it should be set to true here as well to reflect that. +I18n.enforce_available_locales = true RSpec.configure do |config| config.include Rack::Test::Methods From ae879cbb75f4907a98830eccd61df5da51c15062 Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 17 Oct 2018 19:49:58 +0100 Subject: [PATCH 015/290] Remountable APIs, allows to re-mount APIs that inherit from this Creates a new module that can replace a Grape::API in most cases which allows APIs to be re-mounted --- lib/grape.rb | 1 + lib/grape/dsl/routing.rb | 5 +- lib/grape/remountable_api.rb | 64 ++++++++++++++++++++++ spec/grape/remountable_api_spec.rb | 85 ++++++++++++++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 lib/grape/remountable_api.rb create mode 100644 spec/grape/remountable_api_spec.rb diff --git a/lib/grape.rb b/lib/grape.rb index d626ec5c0..9c7ec9998 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -29,6 +29,7 @@ module Grape eager_autoload do autoload :API + autoload :RemountableAPI autoload :Endpoint autoload :Namespace diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index fcde3dbda..6a2f24c6a 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -77,9 +77,12 @@ def do_not_route_options! namespace_inheritable(:do_not_route_options, true) end - def mount(mounts) + def mount(mounts, with: {}) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| + if app.is_a?(Class) && app.ancestors.include?(Grape::RemountableAPI) + return mount(app.new_instance(configuration: with) => path) + end in_setting = inheritable_setting if app.respond_to?(:inheritable_setting, true) diff --git a/lib/grape/remountable_api.rb b/lib/grape/remountable_api.rb new file mode 100644 index 000000000..b51d592d0 --- /dev/null +++ b/lib/grape/remountable_api.rb @@ -0,0 +1,64 @@ +module Grape + # The RemountableAPI class can replace most API classes, except for the base one that is to be mounted in rack. + # should subclass this class in order to build an API. + class RemountableAPI + class << self + # When inherited, will create a list of all instances (times the API was mounted) + # It will listen to the setup required to mount that endpoint, and replicate it on any new instance + def inherited(remountable_class) + remountable_class.instance_variable_set(:@instances, []) + remountable_class.instance_variable_set(:@setup, []) + + base_instance = Class.new(Grape::API) + base_instance.define_singleton_method(:configuration) { {} } + + remountable_class.instance_variable_set(:@base_instance, base_instance) + base_instance.constants.each do |constant_name| + remountable_class.const_set(constant_name, base_instance.const_get(constant_name)) + end + end + + # The remountable class can have a configuration hash to provide some dynamic class-level variables. + # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary + # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration + # too much, you may actually want to provide a new API rather than remount it. + def new_instance(configuration: {}) + instance = Class.new(Grape::API) + instance.instance_variable_set(:@configuration, configuration) + instance.define_singleton_method(:configuration) { @configuration } + replay_setup_on(instance) + @instances << instance + instance + end + + # Replays the set up to produce an API as defined in this class, can be called + # on classes that inherit from Grape::API + def replay_setup_on(instance) + @setup.each do |setup_stage| + instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) + end + end + + private + + # Adds a new stage to the set up require to get a Grape::API up and running + def add_setup(method, *args, &block) + setup_stage = { method: method, args: args, block: block } + @setup << setup_stage + @base_instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) + end + + def method_missing(method, *args, &block) + if respond_to_missing?(method, true) + add_setup(method, *args, &block) + else + super + end + end + + def respond_to_missing?(name, include_private = false) + @base_instance.respond_to?(name, include_private) + end + end + end +end diff --git a/spec/grape/remountable_api_spec.rb b/spec/grape/remountable_api_spec.rb new file mode 100644 index 000000000..bec7f0d08 --- /dev/null +++ b/spec/grape/remountable_api_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' +require 'shared/versioning_examples' + +describe Grape::RemountableAPI do + subject(:a_remountable_api) { Class.new(Grape::RemountableAPI) } + let(:root_api) { Class.new(Grape::API) } + + def app + root_api + end + + describe 'mounted RemountableAPI' do + context 'with a defined route' do + before do + a_remountable_api.get '/votes' do + '10 votes' + end + end + + context 'when mounting one instance' do + before do + root_api.mount a_remountable_api + end + + it 'can access the endpoint' do + get '/votes' + expect(last_response.body).to eql '10 votes' + end + end + + context 'when mounting twice' do + before do + root_api.mount a_remountable_api => '/posts' + root_api.mount a_remountable_api => '/comments' + end + + it 'can access the votes in both places' do + get '/posts/votes' + expect(last_response.body).to eql '10 votes' + get '/comments/votes' + expect(last_response.body).to eql '10 votes' + end + end + + context 'when mounting on namespace' do + before do + stub_const('StaticRefToAPI', a_remountable_api) + root_api.namespace 'posts' do + mount StaticRefToAPI + end + + root_api.namespace 'comments' do + mount StaticRefToAPI + end + end + + it 'can access the votes in both places' do + get '/posts/votes' + expect(last_response.body).to eql '10 votes' + get '/comments/votes' + expect(last_response.body).to eql '10 votes' + end + end + end + + context 'with a dynamically configured route' do + before do + a_remountable_api.namespace 'api' do + get "/#{configuration[:path]}" do + '10 votes' + end + end + root_api.mount a_remountable_api, with: { path: 'votes' } + root_api.mount a_remountable_api, with: { path: 'scores' } + end + + it 'will use the dynamic configuration on all routes' do + get 'api/votes' + expect(last_response.body).to eql '10 votes' + get 'api/scores' + expect(last_response.body).to eql '10 votes' + end + end + end +end From 091a331d75d4c4729412ad0ef773d59007a20dda Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 17 Oct 2018 20:12:32 +0100 Subject: [PATCH 016/290] Adds Readme.md to remountable APIs --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/README.md b/README.md index 20e6bf11c..fde198335 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,59 @@ class Twitter::API < Grape::API end ``` +## Remounting + +You can mount the same endpoints in two different locations using `RemountableAPI` + +```ruby +class Voting::API < Grape::RemountableAPI + namespace 'votes' do + get do + # Your logic + end + + post do + # Your logic + end + end +end + +class Post::API < Grape::API + mount Voting::API +end + +class Comment::API < Grape::API + mount Voting::API +end +``` + +Assuming that the post and comment endpoints are mounted in `/posts` and `/comments`, +you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes` + +### Configuration + +You can configure remountable endpoints for small details changing according to where +they are mounted + +```ruby +class Voting::API < Grape::RemountableAPI + namespace 'votes' do + desc "Vote for your #{configuration[:votable]}" + get do + # Your logic + end + end +end + +class Post::API < Grape::API + mount Voting::API, with: {votable: 'posts'} +end + +class Comment::API < Grape::API + mount Voting::API, with: {votable: 'comments'} +end +``` + ## Versioning There are four strategies in which clients can reach your API's endpoints: `:path`, From ccec54354a9ccd1d3f97e826bfd1009052697fd5 Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 17 Oct 2018 20:14:31 +0100 Subject: [PATCH 017/290] Adds Changelog.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70b91a4cb..e82864b94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1802](https://github.com/ruby-grape/grape/pull/1802): Adds the ability to re-mount endpoints that inherit from RemountableAPI - [@myxoh](https://github.com/myxoh). * [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck). * [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469). From 641959c4fa1a9c1292ae7aad5699a407e1f9a306 Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 17 Oct 2018 20:23:49 +0100 Subject: [PATCH 018/290] Adds remounting section to the table of contents --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index fde198335..958844946 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ - [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks) - [Rails](#rails) - [Modules](#modules) +- [Remounting](#remounting) + - [Configuration](#configuration) - [Versioning](#versioning) - [Path](#path) - [Header](#header) From 37cce04541560d038e03ebf3b3c35d99554714a9 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 14:15:53 +0100 Subject: [PATCH 019/290] Makes Remountable API a valid drop-in replacement to Grape::API --- lib/grape/dsl/routing.rb | 4 +-- lib/grape/remountable_api.rb | 66 +++++++++++++++++++++++------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index 6a2f24c6a..15ad8cb6e 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -80,8 +80,8 @@ def do_not_route_options! def mount(mounts, with: {}) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| - if app.is_a?(Class) && app.ancestors.include?(Grape::RemountableAPI) - return mount(app.new_instance(configuration: with) => path) + if app.respond_to?(:mount_instance) + return mount(app.mount_instance(configuration: with) => path) end in_setting = inheritable_setting diff --git a/lib/grape/remountable_api.rb b/lib/grape/remountable_api.rb index b51d592d0..26f263458 100644 --- a/lib/grape/remountable_api.rb +++ b/lib/grape/remountable_api.rb @@ -1,20 +1,44 @@ +require 'grape/router' + module Grape - # The RemountableAPI class can replace most API classes, except for the base one that is to be mounted in rack. + # The RemountableAPI class can replace all API classes # should subclass this class in order to build an API. class RemountableAPI + # Class methods that we want to call on the RemountableAPI rather than on the API object + NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to?].freeze + class << self + attr_accessor :base_instance # When inherited, will create a list of all instances (times the API was mounted) # It will listen to the setup required to mount that endpoint, and replicate it on any new instance - def inherited(remountable_class) - remountable_class.instance_variable_set(:@instances, []) - remountable_class.instance_variable_set(:@setup, []) + def inherited(remountable_class, base_instance_parent = Grape::API) + remountable_class.initial_setup(base_instance_parent) + remountable_class.override_all_methods + remountable_class.make_inheritable + end - base_instance = Class.new(Grape::API) - base_instance.define_singleton_method(:configuration) { {} } + # Initialize the instance variables on the remountable class, and the base_instance + # an instance that will be used to create the set up but will not be mounted + def initial_setup(base_instance_parent) + @instances = [] + @setup = [] + @base_parent = base_instance_parent + @base_instance = mount_instance + end + + # Redefines all methods so that are forwarded to add_setup and recorded + def override_all_methods + (base_instance.methods - NON_OVERRIDABLE).each do |method_override| + define_singleton_method(method_override) do |*args, &block| + add_setup(method_override, *args, &block) + end + end + end - remountable_class.instance_variable_set(:@base_instance, base_instance) - base_instance.constants.each do |constant_name| - remountable_class.const_set(constant_name, base_instance.const_get(constant_name)) + # When classes inheriting from this RemountableAPI child, we also want the instances to inherit from our instance + def make_inheritable + define_singleton_method(:inherited) do |sub_remountable| + Grape::RemountableAPI.inherited(sub_remountable, base_instance) end end @@ -22,8 +46,8 @@ def inherited(remountable_class) # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration # too much, you may actually want to provide a new API rather than remount it. - def new_instance(configuration: {}) - instance = Class.new(Grape::API) + def mount_instance(configuration: {}) + instance = Class.new(@base_parent) instance.instance_variable_set(:@configuration, configuration) instance.define_singleton_method(:configuration) { @configuration } replay_setup_on(instance) @@ -39,25 +63,21 @@ def replay_setup_on(instance) end end + def respond_to?(method, include_private = false) + super(method, include_private) || base_instance.respond_to?(method, include_private) + end + private # Adds a new stage to the set up require to get a Grape::API up and running def add_setup(method, *args, &block) setup_stage = { method: method, args: args, block: block } @setup << setup_stage - @base_instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) - end - - def method_missing(method, *args, &block) - if respond_to_missing?(method, true) - add_setup(method, *args, &block) - else - super + last_response = nil + @instances.each do |instance| + last_response = instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) end - end - - def respond_to_missing?(name, include_private = false) - @base_instance.respond_to?(name, include_private) + last_response end end end From cf14fc79d0100b988fb0d5ccd9182e3135e9ae48 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 14:13:05 +0100 Subject: [PATCH 020/290] Replaces APIs with RemountableAPI Fixes all remaining issues --- .rubocop_todo.yml | 2 +- lib/grape.rb | 2 +- lib/grape/api.rb | 257 ++++++------------------- lib/grape/api_instance.rb | 233 ++++++++++++++++++++++ lib/grape/remountable_api.rb | 84 -------- spec/grape/api_spec.rb | 2 +- spec/grape/middleware/auth/dsl_spec.rb | 6 +- spec/grape/remountable_api_spec.rb | 4 +- 8 files changed, 295 insertions(+), 295 deletions(-) create mode 100644 lib/grape/api_instance.rb delete mode 100644 lib/grape/remountable_api.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8052a8a6d..beb6a1b48 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -87,7 +87,7 @@ Naming/HeredocDelimiterNaming: # Configuration parameters: AutoCorrect. Performance/HashEachMethods: Exclude: - - 'lib/grape/api.rb' + - 'lib/grape/api_instance.rb' - 'lib/grape/middleware/versioner/header.rb' # Offense count: 1 diff --git a/lib/grape.rb b/lib/grape.rb index 9c7ec9998..91136f0be 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -28,8 +28,8 @@ module Grape extend ::ActiveSupport::Autoload eager_autoload do + autoload :APIInstance autoload :API - autoload :RemountableAPI autoload :Endpoint autoload :Namespace diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 70114859d..eadf33312 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -1,233 +1,84 @@ require 'grape/router' module Grape - # The API class is the primary entry point for creating Grape APIs. Users + # The RemountableAPI class can replace most API classes, except for the base one that is to be mounted in rack. # should subclass this class in order to build an API. class API - include Grape::DSL::API + # Class methods that we want to call on the RemountableAPI rather than on the API object + NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? byebug].freeze class << self - attr_reader :instance - - # A class-level lock to ensure the API is not compiled by multiple - # threads simultaneously within the same process. - LOCK = Mutex.new - - # Clears all defined routes, endpoints, etc., on this API. - def reset! - reset_endpoints! - reset_routes! - reset_validations! - end - - # Parses the API's definition and compiles it into an instance of - # Grape::API. - def compile - @instance ||= new + attr_accessor :base_instance + # When inherited, will create a list of all instances (times the API was mounted) + # It will listen to the setup required to mount that endpoint, and replicate it on any new instance + def inherited(remountable_class, base_instance_parent = Grape::APIInstance) + remountable_class.initial_setup(base_instance_parent) + remountable_class.override_all_methods + remountable_class.make_inheritable end - # Wipe the compiled API so we can recompile after changes were made. - def change! - @instance = nil + # Initialize the instance variables on the remountable class, and the base_instance + # an instance that will be used to create the set up but will not be mounted + def initial_setup(base_instance_parent) + @instances = [] + @setup = [] + @base_parent = base_instance_parent + @base_instance = mount_instance end - # This is the interface point between Rack and Grape; it accepts a request - # from Rack and ultimately returns an array of three values: the status, - # the headers, and the body. See [the rack specification] - # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. - def call(env) - LOCK.synchronize { compile } unless instance - call!(env) - end - - # A non-synchronized version of ::call. - def call!(env) - instance.call(env) - end - - # (see #cascade?) - def cascade(value = nil) - if value.nil? - inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true - else - namespace_inheritable(:cascade, value) + # Redefines all methods so that are forwarded to add_setup and recorded + def override_all_methods + (base_instance.methods - NON_OVERRIDABLE).each do |method_override| + define_singleton_method(method_override) do |*args, &block| + add_setup(method_override, *args, &block) + end end end - # see Grape::Router#recognize_path - def recognize_path(path) - LOCK.synchronize { compile } unless instance - instance.router.recognize_path(path) - end - - protected - - def prepare_routes - endpoints.map(&:routes).flatten - end - - # Execute first the provided block, then each of the - # block passed in. Allows for simple 'before' setups - # of settings stack pushes. - def nest(*blocks, &block) - blocks.reject!(&:nil?) - if blocks.any? - instance_eval(&block) if block_given? - blocks.each { |b| instance_eval(&b) } - reset_validations! - else - instance_eval(&block) + # When classes inheriting from this RemountableAPI child, we also want the instances to inherit from our instance + def make_inheritable + define_singleton_method(:inherited) do |sub_remountable| + Grape::API.inherited(sub_remountable, base_instance) end end - def inherited(subclass) - subclass.reset! - subclass.logger = logger.clone + # The remountable class can have a configuration hash to provide some dynamic class-level variables. + # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary + # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration + # too much, you may actually want to provide a new API rather than remount it. + def mount_instance(configuration: {}) + instance = Class.new(@base_parent) + instance.instance_variable_set(:@configuration, configuration) + instance.define_singleton_method(:configuration) { @configuration } + replay_setup_on(instance) + @instances << instance + instance end - def inherit_settings(other_settings) - top_level_setting.inherit_from other_settings.point_in_time_copy - - # Propagate any inherited params down to our endpoints, and reset any - # compiled routes. - endpoints.each do |e| - e.inherit_settings(top_level_setting.namespace_stackable) - e.reset_routes! + # Replays the set up to produce an API as defined in this class, can be called + # on classes that inherit from Grape::API + def replay_setup_on(instance) + @setup.each do |setup_stage| + instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) end - - reset_routes! end - end - - attr_reader :router - # Builds the routes from the defined endpoints, effectively compiling - # this API into a usable form. - def initialize - @router = Router.new - add_head_not_allowed_methods_and_options_methods - self.class.endpoints.each do |endpoint| - endpoint.mount_in(@router) + def respond_to?(method, include_private = false) + super(method, include_private) || base_instance.respond_to?(method, include_private) end - @router.compile! - @router.freeze - end - - # Handle a request. See Rack documentation for what `env` is. - def call(env) - result = @router.call(env) - result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade? - result - end - - # Some requests may return a HTTP 404 error if grape cannot find a matching - # route. In this case, Grape::Router adds a X-Cascade header to the response - # and sets it to 'pass', indicating to grape's parents they should keep - # looking for a matching route on other resources. - # - # In some applications (e.g. mounting grape on rails), one might need to trap - # errors from reaching upstream. This is effectivelly done by unsetting - # X-Cascade. Default :cascade is true. - def cascade? - return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade) - return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade) - true - end - - reset! - - private + private - # For every resource add a 'OPTIONS' route that returns an HTTP 204 response - # with a list of HTTP methods that can be called. Also add a route that - # will return an HTTP 405 response for any HTTP method that the resource - # cannot handle. - def add_head_not_allowed_methods_and_options_methods - routes_map = {} - - self.class.endpoints.each do |endpoint| - routes = endpoint.routes - routes.each do |route| - # using the :any shorthand produces [nil] for route methods, substitute all manually - route_key = route.pattern.to_regexp - routes_map[route_key] ||= {} - route_settings = routes_map[route_key] - route_settings[:pattern] = route.pattern - route_settings[:requirements] = route.requirements - route_settings[:path] = route.origin - route_settings[:methods] ||= [] - route_settings[:methods] << route.request_method - route_settings[:endpoint] = route.app - - # using the :any shorthand produces [nil] for route methods, substitute all manually - route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*') - end - end - - # The paths we collected are prepared (cf. Path#prepare), so they - # contain already versioning information when using path versioning. - # Disable versioning so adding a route won't prepend versioning - # informations again. - without_root_prefix do - without_versioning do - routes_map.each do |_, config| - methods = config[:methods] - allowed_methods = methods.dup - - unless self.class.namespace_inheritable(:do_not_route_head) - allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET) - end - - allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ') - - unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS) - config[:endpoint].options[:options_route_enabled] = true - end - - attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header) - generate_not_allowed_method(config[:pattern], attributes) - end + # Adds a new stage to the set up require to get a Grape::API up and running + def add_setup(method, *args, &block) + setup_stage = { method: method, args: args, block: block } + @setup << setup_stage + last_response = nil + @instances.each do |instance| + last_response = instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) end + last_response end end - - # Generate a route that returns an HTTP 405 response for a user defined - # path on methods not specified - def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) - not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods - not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options) - - return if not_allowed_methods.empty? - - @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) - end - - # Allows definition of endpoints that ignore the versioning configuration - # used by the rest of your API. - def without_versioning(&_block) - old_version = self.class.namespace_inheritable(:version) - old_version_options = self.class.namespace_inheritable(:version_options) - - self.class.namespace_inheritable_to_nil(:version) - self.class.namespace_inheritable_to_nil(:version_options) - - yield - - self.class.namespace_inheritable(:version, old_version) - self.class.namespace_inheritable(:version_options, old_version_options) - end - - # Allows definition of endpoints that ignore the root prefix used by the - # rest of your API. - def without_root_prefix(&_block) - old_prefix = self.class.namespace_inheritable(:root_prefix) - - self.class.namespace_inheritable_to_nil(:root_prefix) - - yield - - self.class.namespace_inheritable(:root_prefix, old_prefix) - end end end diff --git a/lib/grape/api_instance.rb b/lib/grape/api_instance.rb new file mode 100644 index 000000000..fea94962a --- /dev/null +++ b/lib/grape/api_instance.rb @@ -0,0 +1,233 @@ +require 'grape/router' + +module Grape + # The API class is the primary entry point for creating Grape APIs. Users + # should subclass this class in order to build an API. + class APIInstance + include Grape::DSL::API + + class << self + attr_reader :instance + + # A class-level lock to ensure the API is not compiled by multiple + # threads simultaneously within the same process. + LOCK = Mutex.new + + # Clears all defined routes, endpoints, etc., on this API. + def reset! + reset_endpoints! + reset_routes! + reset_validations! + end + + # Parses the API's definition and compiles it into an instance of + # Grape::API. + def compile + @instance ||= new + end + + # Wipe the compiled API so we can recompile after changes were made. + def change! + @instance = nil + end + + # This is the interface point between Rack and Grape; it accepts a request + # from Rack and ultimately returns an array of three values: the status, + # the headers, and the body. See [the rack specification] + # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. + def call(env) + LOCK.synchronize { compile } unless instance + call!(env) + end + + # A non-synchronized version of ::call. + def call!(env) + instance.call(env) + end + + # (see #cascade?) + def cascade(value = nil) + if value.nil? + inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true + else + namespace_inheritable(:cascade, value) + end + end + + # see Grape::Router#recognize_path + def recognize_path(path) + LOCK.synchronize { compile } unless instance + instance.router.recognize_path(path) + end + + protected + + def prepare_routes + endpoints.map(&:routes).flatten + end + + # Execute first the provided block, then each of the + # block passed in. Allows for simple 'before' setups + # of settings stack pushes. + def nest(*blocks, &block) + blocks.reject!(&:nil?) + if blocks.any? + instance_eval(&block) if block_given? + blocks.each { |b| instance_eval(&b) } + reset_validations! + else + instance_eval(&block) + end + end + + def inherited(subclass) + subclass.reset! + subclass.logger = logger.clone + end + + def inherit_settings(other_settings) + top_level_setting.inherit_from other_settings.point_in_time_copy + + # Propagate any inherited params down to our endpoints, and reset any + # compiled routes. + endpoints.each do |e| + e.inherit_settings(top_level_setting.namespace_stackable) + e.reset_routes! + end + + reset_routes! + end + end + + attr_reader :router + + # Builds the routes from the defined endpoints, effectively compiling + # this API into a usable form. + def initialize + @router = Router.new + add_head_not_allowed_methods_and_options_methods + self.class.endpoints.each do |endpoint| + endpoint.mount_in(@router) + end + + @router.compile! + @router.freeze + end + + # Handle a request. See Rack documentation for what `env` is. + def call(env) + result = @router.call(env) + result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade? + result + end + + # Some requests may return a HTTP 404 error if grape cannot find a matching + # route. In this case, Grape::Router adds a X-Cascade header to the response + # and sets it to 'pass', indicating to grape's parents they should keep + # looking for a matching route on other resources. + # + # In some applications (e.g. mounting grape on rails), one might need to trap + # errors from reaching upstream. This is effectivelly done by unsetting + # X-Cascade. Default :cascade is true. + def cascade? + return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade) + return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade) + true + end + + reset! + + private + + # For every resource add a 'OPTIONS' route that returns an HTTP 204 response + # with a list of HTTP methods that can be called. Also add a route that + # will return an HTTP 405 response for any HTTP method that the resource + # cannot handle. + def add_head_not_allowed_methods_and_options_methods + routes_map = {} + + self.class.endpoints.each do |endpoint| + routes = endpoint.routes + routes.each do |route| + # using the :any shorthand produces [nil] for route methods, substitute all manually + route_key = route.pattern.to_regexp + routes_map[route_key] ||= {} + route_settings = routes_map[route_key] + route_settings[:pattern] = route.pattern + route_settings[:requirements] = route.requirements + route_settings[:path] = route.origin + route_settings[:methods] ||= [] + route_settings[:methods] << route.request_method + route_settings[:endpoint] = route.app + + # using the :any shorthand produces [nil] for route methods, substitute all manually + route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*') + end + end + + # The paths we collected are prepared (cf. Path#prepare), so they + # contain already versioning information when using path versioning. + # Disable versioning so adding a route won't prepend versioning + # informations again. + without_root_prefix do + without_versioning do + routes_map.each do |_, config| + methods = config[:methods] + allowed_methods = methods.dup + + unless self.class.namespace_inheritable(:do_not_route_head) + allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET) + end + + allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ') + + unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS) + config[:endpoint].options[:options_route_enabled] = true + end + + attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header) + generate_not_allowed_method(config[:pattern], attributes) + end + end + end + end + + # Generate a route that returns an HTTP 405 response for a user defined + # path on methods not specified + def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) + not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods + not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options) + + return if not_allowed_methods.empty? + + @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) + end + + # Allows definition of endpoints that ignore the versioning configuration + # used by the rest of your API. + def without_versioning(&_block) + old_version = self.class.namespace_inheritable(:version) + old_version_options = self.class.namespace_inheritable(:version_options) + + self.class.namespace_inheritable_to_nil(:version) + self.class.namespace_inheritable_to_nil(:version_options) + + yield + + self.class.namespace_inheritable(:version, old_version) + self.class.namespace_inheritable(:version_options, old_version_options) + end + + # Allows definition of endpoints that ignore the root prefix used by the + # rest of your API. + def without_root_prefix(&_block) + old_prefix = self.class.namespace_inheritable(:root_prefix) + + self.class.namespace_inheritable_to_nil(:root_prefix) + + yield + + self.class.namespace_inheritable(:root_prefix, old_prefix) + end + end +end diff --git a/lib/grape/remountable_api.rb b/lib/grape/remountable_api.rb deleted file mode 100644 index 26f263458..000000000 --- a/lib/grape/remountable_api.rb +++ /dev/null @@ -1,84 +0,0 @@ -require 'grape/router' - -module Grape - # The RemountableAPI class can replace all API classes - # should subclass this class in order to build an API. - class RemountableAPI - # Class methods that we want to call on the RemountableAPI rather than on the API object - NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to?].freeze - - class << self - attr_accessor :base_instance - # When inherited, will create a list of all instances (times the API was mounted) - # It will listen to the setup required to mount that endpoint, and replicate it on any new instance - def inherited(remountable_class, base_instance_parent = Grape::API) - remountable_class.initial_setup(base_instance_parent) - remountable_class.override_all_methods - remountable_class.make_inheritable - end - - # Initialize the instance variables on the remountable class, and the base_instance - # an instance that will be used to create the set up but will not be mounted - def initial_setup(base_instance_parent) - @instances = [] - @setup = [] - @base_parent = base_instance_parent - @base_instance = mount_instance - end - - # Redefines all methods so that are forwarded to add_setup and recorded - def override_all_methods - (base_instance.methods - NON_OVERRIDABLE).each do |method_override| - define_singleton_method(method_override) do |*args, &block| - add_setup(method_override, *args, &block) - end - end - end - - # When classes inheriting from this RemountableAPI child, we also want the instances to inherit from our instance - def make_inheritable - define_singleton_method(:inherited) do |sub_remountable| - Grape::RemountableAPI.inherited(sub_remountable, base_instance) - end - end - - # The remountable class can have a configuration hash to provide some dynamic class-level variables. - # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary - # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration - # too much, you may actually want to provide a new API rather than remount it. - def mount_instance(configuration: {}) - instance = Class.new(@base_parent) - instance.instance_variable_set(:@configuration, configuration) - instance.define_singleton_method(:configuration) { @configuration } - replay_setup_on(instance) - @instances << instance - instance - end - - # Replays the set up to produce an API as defined in this class, can be called - # on classes that inherit from Grape::API - def replay_setup_on(instance) - @setup.each do |setup_stage| - instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) - end - end - - def respond_to?(method, include_private = false) - super(method, include_private) || base_instance.respond_to?(method, include_private) - end - - private - - # Adds a new stage to the set up require to get a Grape::API up and running - def add_setup(method, *args, &block) - setup_stage = { method: method, args: args, block: block } - @setup << setup_stage - last_response = nil - @instances.each do |instance| - last_response = instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) - end - last_response - end - end - end -end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index df58f88e0..9ff4ea7de 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -3224,7 +3224,7 @@ def static it 'sets the instance' do expect(subject.instance).to be_nil subject.compile - expect(subject.instance).to be_kind_of(subject) + expect(subject.instance).to be_kind_of(subject.base_instance) end end diff --git a/spec/grape/middleware/auth/dsl_spec.rb b/spec/grape/middleware/auth/dsl_spec.rb index 4f969b44e..f39825a60 100644 --- a/spec/grape/middleware/auth/dsl_spec.rb +++ b/spec/grape/middleware/auth/dsl_spec.rb @@ -15,15 +15,15 @@ describe '.auth' do it 'stets auth parameters' do - expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings) + expect(subject.base_instance).to receive(:use).with(Grape::Middleware::Auth::Base, settings) subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc] expect(subject.auth).to eq(settings) end it 'can be called multiple times' do - expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings) - expect(subject).to receive(:use).with(Grape::Middleware::Auth::Base, settings.merge(realm: 'super_secret')) + expect(subject.base_instance).to receive(:use).with(Grape::Middleware::Auth::Base, settings) + expect(subject.base_instance).to receive(:use).with(Grape::Middleware::Auth::Base, settings.merge(realm: 'super_secret')) subject.auth :http_digest, realm: settings[:realm], opaque: settings[:opaque], &settings[:proc] first_settings = subject.auth diff --git a/spec/grape/remountable_api_spec.rb b/spec/grape/remountable_api_spec.rb index bec7f0d08..9f0e036c7 100644 --- a/spec/grape/remountable_api_spec.rb +++ b/spec/grape/remountable_api_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' require 'shared/versioning_examples' -describe Grape::RemountableAPI do - subject(:a_remountable_api) { Class.new(Grape::RemountableAPI) } +describe Grape::API do + subject(:a_remountable_api) { Class.new(Grape::API) } let(:root_api) { Class.new(Grape::API) } def app From 5fcc07bb9d4336f281eed71b8350cc14b5e46731 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 14:30:54 +0100 Subject: [PATCH 021/290] Updates documentation --- CHANGELOG.md | 2 +- README.md | 6 +++--- lib/grape/api.rb | 6 +++--- lib/grape/api_instance.rb | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e82864b94..08ef07c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ #### Features * Your contribution here. -* [#1802](https://github.com/ruby-grape/grape/pull/1802): Adds the ability to re-mount endpoints that inherit from RemountableAPI - [@myxoh](https://github.com/myxoh). +* [#1795](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/bschmeck). * [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck). * [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469). diff --git a/README.md b/README.md index 958844946..1bd21a1d7 100644 --- a/README.md +++ b/README.md @@ -370,10 +370,10 @@ end ## Remounting -You can mount the same endpoints in two different locations using `RemountableAPI` +You can mount the same endpoints in two different locations ```ruby -class Voting::API < Grape::RemountableAPI +class Voting::API < Grape::GrapeAPI namespace 'votes' do get do # Your logic @@ -403,7 +403,7 @@ You can configure remountable endpoints for small details changing according to they are mounted ```ruby -class Voting::API < Grape::RemountableAPI +class Voting::API < Grape::API namespace 'votes' do desc "Vote for your #{configuration[:votable]}" get do diff --git a/lib/grape/api.rb b/lib/grape/api.rb index eadf33312..507a97982 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -1,10 +1,10 @@ require 'grape/router' module Grape - # The RemountableAPI class can replace most API classes, except for the base one that is to be mounted in rack. + # The API class is the primary entry point for creating Grape APIs. Users # should subclass this class in order to build an API. class API - # Class methods that we want to call on the RemountableAPI rather than on the API object + # Class methods that we want to call on the API rather than on the API object NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? byebug].freeze class << self @@ -35,7 +35,7 @@ def override_all_methods end end - # When classes inheriting from this RemountableAPI child, we also want the instances to inherit from our instance + # When classes inheriting from this API child, we also want the instances to inherit from our instance def make_inheritable define_singleton_method(:inherited) do |sub_remountable| Grape::API.inherited(sub_remountable, base_instance) diff --git a/lib/grape/api_instance.rb b/lib/grape/api_instance.rb index fea94962a..7016ecd69 100644 --- a/lib/grape/api_instance.rb +++ b/lib/grape/api_instance.rb @@ -1,8 +1,8 @@ require 'grape/router' module Grape - # The API class is the primary entry point for creating Grape APIs. Users - # should subclass this class in order to build an API. + # The API Instance class, is the engine behind Grape::API. Each class that inherits + # from this will represent a different API class APIInstance include Grape::DSL::API From 691eca83987c906fcc3c3bf91b8c3423d930f80a Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 16:27:50 +0100 Subject: [PATCH 022/290] Changes APIInstance to API::Instance --- .rubocop_todo.yml | 2 +- lib/grape.rb | 1 - lib/grape/api.rb | 5 +- lib/grape/api/instance.rb | 235 ++++++++++++++++++++++++++++++++++++++ lib/grape/api_instance.rb | 233 ------------------------------------- 5 files changed, 239 insertions(+), 237 deletions(-) create mode 100644 lib/grape/api/instance.rb delete mode 100644 lib/grape/api_instance.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index beb6a1b48..3441822ea 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -87,7 +87,7 @@ Naming/HeredocDelimiterNaming: # Configuration parameters: AutoCorrect. Performance/HashEachMethods: Exclude: - - 'lib/grape/api_instance.rb' + - 'lib/grape/api/instance.rb' - 'lib/grape/middleware/versioner/header.rb' # Offense count: 1 diff --git a/lib/grape.rb b/lib/grape.rb index 91136f0be..d626ec5c0 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -28,7 +28,6 @@ module Grape extend ::ActiveSupport::Autoload eager_autoload do - autoload :APIInstance autoload :API autoload :Endpoint diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 507a97982..588505beb 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -1,17 +1,18 @@ require 'grape/router' +require 'grape/api/instance' module Grape # The API class is the primary entry point for creating Grape APIs. Users # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? byebug].freeze + NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to?].freeze class << self attr_accessor :base_instance # When inherited, will create a list of all instances (times the API was mounted) # It will listen to the setup required to mount that endpoint, and replicate it on any new instance - def inherited(remountable_class, base_instance_parent = Grape::APIInstance) + def inherited(remountable_class, base_instance_parent = Grape::API::Instance) remountable_class.initial_setup(base_instance_parent) remountable_class.override_all_methods remountable_class.make_inheritable diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb new file mode 100644 index 000000000..179355983 --- /dev/null +++ b/lib/grape/api/instance.rb @@ -0,0 +1,235 @@ +require 'grape/router' + +module Grape + class API + # The API Instance class, is the engine behind Grape::API. Each class that inherits + # from this will represent a different API instance + class Instance + include Grape::DSL::API + + class << self + attr_reader :instance + + # A class-level lock to ensure the API is not compiled by multiple + # threads simultaneously within the same process. + LOCK = Mutex.new + + # Clears all defined routes, endpoints, etc., on this API. + def reset! + reset_endpoints! + reset_routes! + reset_validations! + end + + # Parses the API's definition and compiles it into an instance of + # Grape::API. + def compile + @instance ||= new + end + + # Wipe the compiled API so we can recompile after changes were made. + def change! + @instance = nil + end + + # This is the interface point between Rack and Grape; it accepts a request + # from Rack and ultimately returns an array of three values: the status, + # the headers, and the body. See [the rack specification] + # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. + def call(env) + LOCK.synchronize { compile } unless instance + call!(env) + end + + # A non-synchronized version of ::call. + def call!(env) + instance.call(env) + end + + # (see #cascade?) + def cascade(value = nil) + if value.nil? + inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true + else + namespace_inheritable(:cascade, value) + end + end + + # see Grape::Router#recognize_path + def recognize_path(path) + LOCK.synchronize { compile } unless instance + instance.router.recognize_path(path) + end + + protected + + def prepare_routes + endpoints.map(&:routes).flatten + end + + # Execute first the provided block, then each of the + # block passed in. Allows for simple 'before' setups + # of settings stack pushes. + def nest(*blocks, &block) + blocks.reject!(&:nil?) + if blocks.any? + instance_eval(&block) if block_given? + blocks.each { |b| instance_eval(&b) } + reset_validations! + else + instance_eval(&block) + end + end + + def inherited(subclass) + subclass.reset! + subclass.logger = logger.clone + end + + def inherit_settings(other_settings) + top_level_setting.inherit_from other_settings.point_in_time_copy + + # Propagate any inherited params down to our endpoints, and reset any + # compiled routes. + endpoints.each do |e| + e.inherit_settings(top_level_setting.namespace_stackable) + e.reset_routes! + end + + reset_routes! + end + end + + attr_reader :router + + # Builds the routes from the defined endpoints, effectively compiling + # this API into a usable form. + def initialize + @router = Router.new + add_head_not_allowed_methods_and_options_methods + self.class.endpoints.each do |endpoint| + endpoint.mount_in(@router) + end + + @router.compile! + @router.freeze + end + + # Handle a request. See Rack documentation for what `env` is. + def call(env) + result = @router.call(env) + result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade? + result + end + + # Some requests may return a HTTP 404 error if grape cannot find a matching + # route. In this case, Grape::Router adds a X-Cascade header to the response + # and sets it to 'pass', indicating to grape's parents they should keep + # looking for a matching route on other resources. + # + # In some applications (e.g. mounting grape on rails), one might need to trap + # errors from reaching upstream. This is effectivelly done by unsetting + # X-Cascade. Default :cascade is true. + def cascade? + return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade) + return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade) + true + end + + reset! + + private + + # For every resource add a 'OPTIONS' route that returns an HTTP 204 response + # with a list of HTTP methods that can be called. Also add a route that + # will return an HTTP 405 response for any HTTP method that the resource + # cannot handle. + def add_head_not_allowed_methods_and_options_methods + routes_map = {} + + self.class.endpoints.each do |endpoint| + routes = endpoint.routes + routes.each do |route| + # using the :any shorthand produces [nil] for route methods, substitute all manually + route_key = route.pattern.to_regexp + routes_map[route_key] ||= {} + route_settings = routes_map[route_key] + route_settings[:pattern] = route.pattern + route_settings[:requirements] = route.requirements + route_settings[:path] = route.origin + route_settings[:methods] ||= [] + route_settings[:methods] << route.request_method + route_settings[:endpoint] = route.app + + # using the :any shorthand produces [nil] for route methods, substitute all manually + route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*') + end + end + + # The paths we collected are prepared (cf. Path#prepare), so they + # contain already versioning information when using path versioning. + # Disable versioning so adding a route won't prepend versioning + # informations again. + without_root_prefix do + without_versioning do + routes_map.each do |_, config| + methods = config[:methods] + allowed_methods = methods.dup + + unless self.class.namespace_inheritable(:do_not_route_head) + allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET) + end + + allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ') + + unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS) + config[:endpoint].options[:options_route_enabled] = true + end + + attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header) + generate_not_allowed_method(config[:pattern], attributes) + end + end + end + end + + # Generate a route that returns an HTTP 405 response for a user defined + # path on methods not specified + def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) + not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods + not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options) + + return if not_allowed_methods.empty? + + @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) + end + + # Allows definition of endpoints that ignore the versioning configuration + # used by the rest of your API. + def without_versioning(&_block) + old_version = self.class.namespace_inheritable(:version) + old_version_options = self.class.namespace_inheritable(:version_options) + + self.class.namespace_inheritable_to_nil(:version) + self.class.namespace_inheritable_to_nil(:version_options) + + yield + + self.class.namespace_inheritable(:version, old_version) + self.class.namespace_inheritable(:version_options, old_version_options) + end + + # Allows definition of endpoints that ignore the root prefix used by the + # rest of your API. + def without_root_prefix(&_block) + old_prefix = self.class.namespace_inheritable(:root_prefix) + + self.class.namespace_inheritable_to_nil(:root_prefix) + + yield + + self.class.namespace_inheritable(:root_prefix, old_prefix) + end + end + end +end diff --git a/lib/grape/api_instance.rb b/lib/grape/api_instance.rb deleted file mode 100644 index 7016ecd69..000000000 --- a/lib/grape/api_instance.rb +++ /dev/null @@ -1,233 +0,0 @@ -require 'grape/router' - -module Grape - # The API Instance class, is the engine behind Grape::API. Each class that inherits - # from this will represent a different API - class APIInstance - include Grape::DSL::API - - class << self - attr_reader :instance - - # A class-level lock to ensure the API is not compiled by multiple - # threads simultaneously within the same process. - LOCK = Mutex.new - - # Clears all defined routes, endpoints, etc., on this API. - def reset! - reset_endpoints! - reset_routes! - reset_validations! - end - - # Parses the API's definition and compiles it into an instance of - # Grape::API. - def compile - @instance ||= new - end - - # Wipe the compiled API so we can recompile after changes were made. - def change! - @instance = nil - end - - # This is the interface point between Rack and Grape; it accepts a request - # from Rack and ultimately returns an array of three values: the status, - # the headers, and the body. See [the rack specification] - # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. - def call(env) - LOCK.synchronize { compile } unless instance - call!(env) - end - - # A non-synchronized version of ::call. - def call!(env) - instance.call(env) - end - - # (see #cascade?) - def cascade(value = nil) - if value.nil? - inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true - else - namespace_inheritable(:cascade, value) - end - end - - # see Grape::Router#recognize_path - def recognize_path(path) - LOCK.synchronize { compile } unless instance - instance.router.recognize_path(path) - end - - protected - - def prepare_routes - endpoints.map(&:routes).flatten - end - - # Execute first the provided block, then each of the - # block passed in. Allows for simple 'before' setups - # of settings stack pushes. - def nest(*blocks, &block) - blocks.reject!(&:nil?) - if blocks.any? - instance_eval(&block) if block_given? - blocks.each { |b| instance_eval(&b) } - reset_validations! - else - instance_eval(&block) - end - end - - def inherited(subclass) - subclass.reset! - subclass.logger = logger.clone - end - - def inherit_settings(other_settings) - top_level_setting.inherit_from other_settings.point_in_time_copy - - # Propagate any inherited params down to our endpoints, and reset any - # compiled routes. - endpoints.each do |e| - e.inherit_settings(top_level_setting.namespace_stackable) - e.reset_routes! - end - - reset_routes! - end - end - - attr_reader :router - - # Builds the routes from the defined endpoints, effectively compiling - # this API into a usable form. - def initialize - @router = Router.new - add_head_not_allowed_methods_and_options_methods - self.class.endpoints.each do |endpoint| - endpoint.mount_in(@router) - end - - @router.compile! - @router.freeze - end - - # Handle a request. See Rack documentation for what `env` is. - def call(env) - result = @router.call(env) - result[1].delete(Grape::Http::Headers::X_CASCADE) unless cascade? - result - end - - # Some requests may return a HTTP 404 error if grape cannot find a matching - # route. In this case, Grape::Router adds a X-Cascade header to the response - # and sets it to 'pass', indicating to grape's parents they should keep - # looking for a matching route on other resources. - # - # In some applications (e.g. mounting grape on rails), one might need to trap - # errors from reaching upstream. This is effectivelly done by unsetting - # X-Cascade. Default :cascade is true. - def cascade? - return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade) - return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade) - true - end - - reset! - - private - - # For every resource add a 'OPTIONS' route that returns an HTTP 204 response - # with a list of HTTP methods that can be called. Also add a route that - # will return an HTTP 405 response for any HTTP method that the resource - # cannot handle. - def add_head_not_allowed_methods_and_options_methods - routes_map = {} - - self.class.endpoints.each do |endpoint| - routes = endpoint.routes - routes.each do |route| - # using the :any shorthand produces [nil] for route methods, substitute all manually - route_key = route.pattern.to_regexp - routes_map[route_key] ||= {} - route_settings = routes_map[route_key] - route_settings[:pattern] = route.pattern - route_settings[:requirements] = route.requirements - route_settings[:path] = route.origin - route_settings[:methods] ||= [] - route_settings[:methods] << route.request_method - route_settings[:endpoint] = route.app - - # using the :any shorthand produces [nil] for route methods, substitute all manually - route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*') - end - end - - # The paths we collected are prepared (cf. Path#prepare), so they - # contain already versioning information when using path versioning. - # Disable versioning so adding a route won't prepend versioning - # informations again. - without_root_prefix do - without_versioning do - routes_map.each do |_, config| - methods = config[:methods] - allowed_methods = methods.dup - - unless self.class.namespace_inheritable(:do_not_route_head) - allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET) - end - - allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ') - - unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS) - config[:endpoint].options[:options_route_enabled] = true - end - - attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header) - generate_not_allowed_method(config[:pattern], attributes) - end - end - end - end - - # Generate a route that returns an HTTP 405 response for a user defined - # path on methods not specified - def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) - not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods - not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options) - - return if not_allowed_methods.empty? - - @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) - end - - # Allows definition of endpoints that ignore the versioning configuration - # used by the rest of your API. - def without_versioning(&_block) - old_version = self.class.namespace_inheritable(:version) - old_version_options = self.class.namespace_inheritable(:version_options) - - self.class.namespace_inheritable_to_nil(:version) - self.class.namespace_inheritable_to_nil(:version_options) - - yield - - self.class.namespace_inheritable(:version, old_version) - self.class.namespace_inheritable(:version_options, old_version_options) - end - - # Allows definition of endpoints that ignore the root prefix used by the - # rest of your API. - def without_root_prefix(&_block) - old_prefix = self.class.namespace_inheritable(:root_prefix) - - self.class.namespace_inheritable_to_nil(:root_prefix) - - yield - - self.class.namespace_inheritable(:root_prefix, old_prefix) - end - end -end From ffe147b1bb0ff4158e5f3e99f16fa8136035768f Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 16:28:18 +0100 Subject: [PATCH 023/290] Fixes README an specs descriptions --- README.md | 6 +++--- ...ntable_api_spec.rb => api_remount_spec.rb} | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) rename spec/grape/{remountable_api_spec.rb => api_remount_spec.rb} (76%) diff --git a/README.md b/README.md index 1bd21a1d7..5ea8be5b8 100644 --- a/README.md +++ b/README.md @@ -373,7 +373,7 @@ end You can mount the same endpoints in two different locations ```ruby -class Voting::API < Grape::GrapeAPI +class Voting::API < Grape::API namespace 'votes' do get do # Your logic @@ -413,11 +413,11 @@ class Voting::API < Grape::API end class Post::API < Grape::API - mount Voting::API, with: {votable: 'posts'} + mount Voting::API, with: { votable: 'posts' } end class Comment::API < Grape::API - mount Voting::API, with: {votable: 'comments'} + mount Voting::API, with: { votable: 'comments' } end ``` diff --git a/spec/grape/remountable_api_spec.rb b/spec/grape/api_remount_spec.rb similarity index 76% rename from spec/grape/remountable_api_spec.rb rename to spec/grape/api_remount_spec.rb index 9f0e036c7..76d2625fd 100644 --- a/spec/grape/remountable_api_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -2,24 +2,24 @@ require 'shared/versioning_examples' describe Grape::API do - subject(:a_remountable_api) { Class.new(Grape::API) } + subject(:a_remounted_api) { Class.new(Grape::API) } let(:root_api) { Class.new(Grape::API) } def app root_api end - describe 'mounted RemountableAPI' do + describe 'remounting an API' do context 'with a defined route' do before do - a_remountable_api.get '/votes' do + a_remounted_api.get '/votes' do '10 votes' end end context 'when mounting one instance' do before do - root_api.mount a_remountable_api + root_api.mount a_remounted_api end it 'can access the endpoint' do @@ -30,8 +30,8 @@ def app context 'when mounting twice' do before do - root_api.mount a_remountable_api => '/posts' - root_api.mount a_remountable_api => '/comments' + root_api.mount a_remounted_api => '/posts' + root_api.mount a_remounted_api => '/comments' end it 'can access the votes in both places' do @@ -44,7 +44,7 @@ def app context 'when mounting on namespace' do before do - stub_const('StaticRefToAPI', a_remountable_api) + stub_const('StaticRefToAPI', a_remounted_api) root_api.namespace 'posts' do mount StaticRefToAPI end @@ -65,13 +65,13 @@ def app context 'with a dynamically configured route' do before do - a_remountable_api.namespace 'api' do + a_remounted_api.namespace 'api' do get "/#{configuration[:path]}" do '10 votes' end end - root_api.mount a_remountable_api, with: { path: 'votes' } - root_api.mount a_remountable_api, with: { path: 'scores' } + root_api.mount a_remounted_api, with: { path: 'votes' } + root_api.mount a_remounted_api, with: { path: 'scores' } end it 'will use the dynamic configuration on all routes' do From 1c6a9dc8723775191c4bfc02d05da0ce68ec9948 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 17:07:04 +0100 Subject: [PATCH 024/290] Replaces keyword args with opts --- lib/grape/api.rb | 4 ++-- lib/grape/dsl/routing.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 588505beb..1b39ba319 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -47,9 +47,9 @@ def make_inheritable # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration # too much, you may actually want to provide a new API rather than remount it. - def mount_instance(configuration: {}) + def mount_instance(opts = {}) instance = Class.new(@base_parent) - instance.instance_variable_set(:@configuration, configuration) + instance.instance_variable_set(:@configuration, opts[:configuration] || {}) instance.define_singleton_method(:configuration) { @configuration } replay_setup_on(instance) @instances << instance diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index 15ad8cb6e..b3366a2de 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -77,11 +77,11 @@ def do_not_route_options! namespace_inheritable(:do_not_route_options, true) end - def mount(mounts, with: {}) + def mount(mounts, opts = {}) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:mount_instance) - return mount(app.mount_instance(configuration: with) => path) + return mount(app.mount_instance(configuration: opts[:with] || {}) => path) end in_setting = inheritable_setting From 142151785a06286a61cb3b30b11b47f4906dfdfb Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 19 Oct 2018 17:30:10 +0100 Subject: [PATCH 025/290] Refactors and comments --- lib/grape/api.rb | 29 +++++++++++++++-------------- lib/grape/api/instance.rb | 7 +++++++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 1b39ba319..8ebca158f 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -9,13 +9,13 @@ class API NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to?].freeze class << self - attr_accessor :base_instance + attr_accessor :base_instance, :instances # When inherited, will create a list of all instances (times the API was mounted) # It will listen to the setup required to mount that endpoint, and replicate it on any new instance - def inherited(remountable_class, base_instance_parent = Grape::API::Instance) - remountable_class.initial_setup(base_instance_parent) - remountable_class.override_all_methods - remountable_class.make_inheritable + def inherited(api, base_instance_parent = Grape::API::Instance) + api.initial_setup(base_instance_parent) + api.override_all_methods! + make_inheritable(api) end # Initialize the instance variables on the remountable class, and the base_instance @@ -27,8 +27,8 @@ def initial_setup(base_instance_parent) @base_instance = mount_instance end - # Redefines all methods so that are forwarded to add_setup and recorded - def override_all_methods + # Redefines all methods so that are forwarded to add_setup and be recorded + def override_all_methods! (base_instance.methods - NON_OVERRIDABLE).each do |method_override| define_singleton_method(method_override) do |*args, &block| add_setup(method_override, *args, &block) @@ -36,10 +36,12 @@ def override_all_methods end end - # When classes inheriting from this API child, we also want the instances to inherit from our instance - def make_inheritable - define_singleton_method(:inherited) do |sub_remountable| - Grape::API.inherited(sub_remountable, base_instance) + # Allows an API to itself be inheritable: + def make_inheritable(api) + # When a child API inherits from a parent API. + def api.inherited(child_api) + # The instances of the child API inherit from the instances of the parent API + Grape::API.inherited(child_api, base_instance) end end @@ -49,10 +51,9 @@ def make_inheritable # too much, you may actually want to provide a new API rather than remount it. def mount_instance(opts = {}) instance = Class.new(@base_parent) - instance.instance_variable_set(:@configuration, opts[:configuration] || {}) - instance.define_singleton_method(:configuration) { @configuration } + instance.configuration = opts[:configuration] || {} + instance.base = self replay_setup_on(instance) - @instances << instance instance end diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 179355983..a3df4b083 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -9,6 +9,13 @@ class Instance class << self attr_reader :instance + attr_reader :base + attr_accessor :configuration + + def base=(grape_api) + @base = grape_api + grape_api.instances << self + end # A class-level lock to ensure the API is not compiled by multiple # threads simultaneously within the same process. From 330a6b93a7a89ad6cd9d156f796a22ca322c8393 Mon Sep 17 00:00:00 2001 From: Nico Date: Sat, 20 Oct 2018 17:43:28 +0100 Subject: [PATCH 026/290] Adds test for mounting multiple apps as one Fixes routing to get specs working --- .rubocop_todo.yml | 3 ++- lib/grape/dsl/routing.rb | 3 ++- spec/grape/dsl/routing_spec.rb | 10 ++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3441822ea..2c0b628a0 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -50,7 +50,6 @@ Metrics/CyclomaticComplexity: # URISchemes: http, https Metrics/LineLength: Max: 215 - # Offense count: 57 # Configuration parameters: CountComments. Metrics/MethodLength: @@ -60,6 +59,8 @@ Metrics/MethodLength: # Configuration parameters: CountComments. Metrics/ModuleLength: Max: 212 + Exclude: + - 'spec/grape/dsl/routing_spec.rb' # Offense count: 21 Metrics/PerceivedComplexity: diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index b3366a2de..bf5f4c65a 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -81,7 +81,8 @@ def mount(mounts, opts = {}) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:mount_instance) - return mount(app.mount_instance(configuration: opts[:with] || {}) => path) + mount(app.mount_instance(configuration: opts[:with] || {}) => path) + next end in_setting = inheritable_setting diff --git a/spec/grape/dsl/routing_spec.rb b/spec/grape/dsl/routing_spec.rb index 60bd00bf9..826b6eaa5 100644 --- a/spec/grape/dsl/routing_spec.rb +++ b/spec/grape/dsl/routing_spec.rb @@ -70,6 +70,16 @@ class Dummy expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1', '/app2']) end + + it 'mounts multiple routes at once' do + base_app = Class.new(Grape::API) + app1 = Class.new(Grape::API) + app2 = Class.new(Grape::API) + base_app.mount(app1 => '/app1', app2 => '/app2') + + expect(app1.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app1']) + expect(app2.inheritable_setting.to_hash[:namespace_stackable]).to eq(mount_path: ['/app2']) + end end describe '.route' do From 24295fda612bd0c178776d2b5d94c4ad8f9352bc Mon Sep 17 00:00:00 2001 From: Nico Date: Sat, 20 Oct 2018 17:45:24 +0100 Subject: [PATCH 027/290] Adds paragraph to upgrading explaining how to rollback behaviour --- UPGRADING.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 6edbf2ad1..e5552e3d3 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,15 @@ Upgrading Grape ### Upgrading to >= 1.1.1 +### Changes in the Grape::API class + +In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, +rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced +with a class that can contain several instances of `Grape::API`. + +This changes were done in such a way that no code-changes should be required. +However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`. + #### Changes in rescue_from returned object Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation. From d4eee5bb017f50ca24537f1fc0daa94ea1117cbd Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 23 Oct 2018 12:04:12 +0100 Subject: [PATCH 028/290] Bumps up version and upgrades description --- CHANGELOG.md | 2 +- README.md | 8 +++----- UPGRADING.md | 6 +++--- lib/grape/version.rb | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ef07c78..cb891a753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.1.1 (Next) +### 1.2.0 (Next) #### Features diff --git a/README.md b/README.md index 5ea8be5b8..23a79180c 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,7 @@ end ## Remounting -You can mount the same endpoints in two different locations +You can mount the same endpoints in two different locations. ```ruby class Voting::API < Grape::API @@ -394,13 +394,11 @@ class Comment::API < Grape::API end ``` -Assuming that the post and comment endpoints are mounted in `/posts` and `/comments`, -you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes` +Assuming that the post and comment endpoints are mounted in `/posts` and `/comments`, you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes`. ### Configuration -You can configure remountable endpoints for small details changing according to where -they are mounted +You can configure remountable endpoints for small details changing according to where they are mounted. ```ruby class Voting::API < Grape::API diff --git a/UPGRADING.md b/UPGRADING.md index e5552e3d3..33b62419a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,7 +1,7 @@ Upgrading Grape =============== -### Upgrading to >= 1.1.1 +### Upgrading to >= 1.2.0 ### Changes in the Grape::API class @@ -19,9 +19,9 @@ Grape will now check the object returned from `rescue_from` and ensure that it i ```ruby class Twitter::API < Grape::API rescue_from :all do |e| - # version prior to 1.1.1 + # version prior to 1.2.0 Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish - # 1.1.1 version + # 1.2.0 version Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }) end end diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 6608850f6..9fce83d5f 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.1.1'.freeze + VERSION = '1.2.0'.freeze end From f8c3feeb0f1253e3f609df10278f728eea92fee3 Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 23 Oct 2018 15:35:48 +0100 Subject: [PATCH 029/290] Adds missing hash to UPGRADING.md --- UPGRADING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 33b62419a..b57dd6032 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,7 +3,7 @@ Upgrading Grape ### Upgrading to >= 1.2.0 -### Changes in the Grape::API class +#### Changes in the Grape::API class In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced @@ -21,7 +21,7 @@ class Twitter::API < Grape::API rescue_from :all do |e| # version prior to 1.2.0 Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }).finish - # 1.2.0 version + # 1.2.0 version Rack::Response.new([ e.message ], 500, { 'Content-type' => 'text/error' }) end end From 9416023600842f99ea4f8c6c86a4ce10786dfa7f Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 23 Oct 2018 16:16:10 +0100 Subject: [PATCH 030/290] Address problems with autoloading by searching for constants in instance --- lib/grape/api.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 8ebca158f..9bf40f126 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to?].freeze + NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? const_defined? const_missing].freeze class << self attr_accessor :base_instance, :instances @@ -45,6 +45,15 @@ def api.inherited(child_api) end end + # Alleviates problems with autoloading by tring to search for the constant + def const_missing(*args) + if base_instance.const_defined?(*args) + base_instance.const_missing(*args) + else + super + end + end + # The remountable class can have a configuration hash to provide some dynamic class-level variables. # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration From ce4966def374ca0fbfed95e520373578111c43e8 Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 24 Oct 2018 13:04:10 +0100 Subject: [PATCH 031/290] Better handles constants --- lib/grape/api.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 9bf40f126..63a636ddb 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,8 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? const_defined? const_missing].freeze + NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? + respond_to? const_defined? const_missing parent parent_name name equal? to_s parents].freeze class << self attr_accessor :base_instance, :instances @@ -48,7 +49,9 @@ def api.inherited(child_api) # Alleviates problems with autoloading by tring to search for the constant def const_missing(*args) if base_instance.const_defined?(*args) - base_instance.const_missing(*args) + base_instance.const_get(*args) + elsif parent && parent.const_defined?(*args) + parent.const_get(*args) else super end From 529967fc6838acf8314c7b5ad79201d3b5eecbb6 Mon Sep 17 00:00:00 2001 From: Franklin Yu Date: Thu, 25 Oct 2018 01:14:43 -0400 Subject: [PATCH 032/290] [skip ci] Use HTTPS in README This should fix mixed-content issue in YARD documentations. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20e6bf11c..80ba265b0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Dependency Status](https://gemnasium.com/ruby-grape/grape.svg)](https://gemnasium.com/ruby-grape/grape) [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape) [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master) -[![Inline docs](http://inch-ci.org/github/ruby-grape/grape.svg)](http://inch-ci.org/github/ruby-grape/grape) +[![Inline docs](https://inch-ci.org/github/ruby-grape/grape.svg)](https://inch-ci.org/github/ruby-grape/grape) [![git.legal](https://git.legal/projects/1364/badge.svg "Number of libraries approved")](https://git.legal/projects/1364) [![Join the chat at https://gitter.im/ruby-grape/grape](https://badges.gitter.im/ruby-grape/grape.svg)](https://gitter.im/ruby-grape/grape?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) From ca47bd7abcb9092cabf36208b5855a4342b48b82 Mon Sep 17 00:00:00 2001 From: Franklin Yu Date: Thu, 25 Oct 2018 22:01:19 -0400 Subject: [PATCH 033/290] Add YARD configuration --- .yardopts | 1 + 1 file changed, 1 insertion(+) create mode 100644 .yardopts diff --git a/.yardopts b/.yardopts new file mode 100644 index 000000000..51701cd72 --- /dev/null +++ b/.yardopts @@ -0,0 +1 @@ +--asset grape.png From e323be4f54d6302adc09b9189f34a0c43e272dc7 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Sun, 28 Oct 2018 16:17:53 +0800 Subject: [PATCH 034/290] Add white space between brackets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff4df9d6a..42555d2ee 100644 --- a/README.md +++ b/README.md @@ -1655,7 +1655,7 @@ params do optional :beer optional :wine optional :juice - exactly_one_of :beer, :wine, :juice, message: {exactly_one: "are missing, exactly one parameter is required", mutual_exclusion: "are mutually exclusive, exactly one parameter is required"} + exactly_one_of :beer, :wine, :juice, message: { exactly_one: "are missing, exactly one parameter is required", mutual_exclusion: "are mutually exclusive, exactly one parameter is required" } end ``` @@ -1674,7 +1674,7 @@ end ```ruby params do - requires :int, type: {value: Integer, message: "type cast is invalid" } + requires :int, type: { value: Integer, message: "type cast is invalid" } end ``` From b8b85af12ad4860b6ee4ae2074f864a849400209 Mon Sep 17 00:00:00 2001 From: Darren Date: Wed, 31 Oct 2018 18:46:47 +0800 Subject: [PATCH 035/290] Fix alias on dependent param bug (#1810) --- CHANGELOG.md | 1 + README.md | 12 +++++++++ lib/grape/validations/params_scope.rb | 1 + spec/grape/validations/params_scope_spec.rb | 28 +++++++++++++++++++++ 4 files changed, 42 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb891a753..49b1cc9d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469). * [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox). * [#1788](https://github.com/ruby-grape/grape/pull/1788): Fix route requirements bug - [@darren987469](https://github.com/darren987469), [@darrellnash](https://github.com/darrellnash). +* [#1810](https://github.com/ruby-grape/grape/pull/1810): Fix support in `given` for aliased params - [@darren987469](https://github.com/darren987469). ### 1.1.0 (8/4/2018) diff --git a/README.md b/README.md index 42555d2ee..e3fe7d36f 100644 --- a/README.md +++ b/README.md @@ -1175,6 +1175,18 @@ params do end ``` +You can set alias for parameter: + +```ruby +params do + optional :category, as: :type + given type: ->(val) { val == 'foo' } do + requires :description + end +end +``` + +Note: param in `given` should be the aliased one. In the example, it should be `type`, not `category`. ### Group Options diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index cad7acb48..05c0c8c0a 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -121,6 +121,7 @@ def push_declared_params(attrs, **opts) if opts && opts[:as] @api.route_setting(:aliased_params, @api.route_setting(:aliased_params) || []) @api.route_setting(:aliased_params) << { attrs.first => opts[:as] } + attrs = [opts[:as]] end @declared_params.concat attrs diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index fff6f5df3..9975c66c9 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -497,6 +497,34 @@ def initialize(value) expect(body.keys).to_not include('b') end + it 'allows aliasing of dependent on parameter' do + subject.params do + optional :a, as: :b + given b: ->(val) { val == 'x' } do + requires :c + end + end + subject.get('/') { declared(params) } + + get '/', a: 'x' + expect(last_response.status).to eq 400 + expect(last_response.body).to eq 'c is missing' + + get '/', a: 'y' + expect(last_response.status).to eq 200 + end + + it 'raises an error if the dependent parameter is not the aliased one' do + expect do + subject.params do + optional :a, as: :b + given :a do + requires :c + end + end + end.to raise_error(Grape::Exceptions::UnknownParameter) + end + it 'does not validate nested requires when given is false' do subject.params do requires :a, type: String, allow_blank: false, values: %w[x y z] From f2e0f3e00bcece39717241aad88b03a4bef2282c Mon Sep 17 00:00:00 2001 From: Andrea Campolonghi Date: Tue, 23 Oct 2018 00:54:20 +0200 Subject: [PATCH 036/290] Addes test in failing nested given validation --- spec/grape/validations/params_scope_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 9975c66c9..7e76309aa 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -479,6 +479,22 @@ def initialize(value) end.to_not raise_error end + it 'does not raise an error if when using nested given' do + expect do + subject.params do + optional :a, type: Hash do + requires :b + end + given :a do + requires :c + given :c do + requires :d + end + end + end + end.to_not raise_error + end + it 'allows aliasing of dependent parameters' do subject.params do optional :a From 0b726dd83cda768a79ce29cb4c1c9825a6238104 Mon Sep 17 00:00:00 2001 From: Darren Chang Date: Thu, 1 Nov 2018 09:14:44 +0800 Subject: [PATCH 037/290] Support nested dependent parameters `given` will check whether attribute is declared in its scope. If not, it raises an error. ```ruby optional :a given a: ->(val) { val == 'a' } do optional :b given b: ->(val) { val == 'b' } do requires :c end end ``` In the example, `optional :a` creates a scope. `given a: ...` creates another scope, which parent scope is the one created by `optional :a`, and attributes `:b` declared in `optional :b` is pushed to parent scope. The bug here is `given b: ...` cannot find `:b` in its scope. Since the attribute `:b` is pushed to its parent scope. So, fix the code to check attribute in its parent scope if it has parent scope. --- CHANGELOG.md | 1 + lib/grape/dsl/parameters.rb | 13 +++++++--- spec/grape/validations/params_scope_spec.rb | 27 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b1cc9d4..97513cf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox). * [#1788](https://github.com/ruby-grape/grape/pull/1788): Fix route requirements bug - [@darren987469](https://github.com/darren987469), [@darrellnash](https://github.com/darrellnash). * [#1810](https://github.com/ruby-grape/grape/pull/1810): Fix support in `given` for aliased params - [@darren987469](https://github.com/darren987469). +* [#1811](https://github.com/ruby-grape/grape/pull/1811): Support nested dependent parameters - [@darren987469](https://github.com/darren987469), [@andreacfm](https://github.com/andreacfm). ### 1.1.0 (8/4/2018) diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index bb3196b06..42e80043c 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -211,10 +211,15 @@ def given(*attrs, &block) # block yet. # @return [Boolean] whether the parameter has been defined def declared_param?(param) - # @declared_params also includes hashes of options and such, but those - # won't be flattened out. - @declared_params.flatten.any? do |declared_param| - first_hash_key_or_param(declared_param) == param + if lateral? + # Elements of @declared_params of lateral scope are pushed in @parent. So check them in @parent. + @parent.declared_param?(param) + else + # @declared_params also includes hashes of options and such, but those + # won't be flattened out. + @declared_params.flatten.any? do |declared_param| + first_hash_key_or_param(declared_param) == param + end end end diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 7e76309aa..b561c0414 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -495,6 +495,33 @@ def initialize(value) end.to_not raise_error end + it 'allows nested dependent parameters' do + subject.params do + optional :a + given a: ->(val) { val == 'a' } do + optional :b + given b: ->(val) { val == 'b' } do + optional :c + given c: ->(val) { val == 'c' } do + requires :d + end + end + end + end + subject.get('/') { declared(params).to_json } + + get '/' + expect(last_response.status).to eq 200 + + get '/', a: 'a', b: 'b', c: 'c' + expect(last_response.status).to eq 400 + expect(last_response.body).to eq 'd is missing' + + get '/', a: 'a', b: 'b', c: 'c', d: 'd' + expect(last_response.status).to eq 200 + expect(last_response.body).to eq({ a: 'a', b: 'b', c: 'c', d: 'd' }.to_json) + end + it 'allows aliasing of dependent parameters' do subject.params do optional :a From 173985f127daf54956d913ca614bd6853eed6e59 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Sat, 3 Nov 2018 06:41:13 +0800 Subject: [PATCH 038/290] Update ruby version in travis --- .travis.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index e6a01b439..d500a606d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,46 +4,46 @@ sudo: false matrix: include: - - rvm: 2.4.2 + - rvm: 2.4.5 script: - bundle exec danger - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: Gemfile - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: gemfiles/rack_1.5.2.gemfile - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: gemfiles/multi_json.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_json - - rvm: 2.4.2 + - rvm: 2.4.5 gemfile: gemfiles/multi_xml.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_xml - - rvm: 2.3.5 + - rvm: 2.3.8 gemfile: Gemfile - - rvm: 2.3.5 + - rvm: 2.3.8 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.3.5 + - rvm: 2.3.8 gemfile: gemfiles/rack_1.5.2.gemfile - - rvm: 2.3.5 + - rvm: 2.3.8 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.2.8 + - rvm: 2.2.10 gemfile: Gemfile - - rvm: 2.2.8 + - rvm: 2.2.10 gemfile: gemfiles/rack_1.5.2.gemfile - - rvm: 2.2.8 + - rvm: 2.2.10 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.2.8 + - rvm: 2.2.10 gemfile: gemfiles/rails_4.gemfile - - rvm: 2.2.8 + - rvm: 2.2.10 gemfile: gemfiles/rails_3.gemfile - rvm: ruby-head - rvm: jruby-head From ab0706678cf000ac3df05caf0a9d70f3ad2914d8 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Sat, 3 Nov 2018 07:43:59 +0800 Subject: [PATCH 039/290] Update rails version --- gemfiles/rails_3.gemfile | 2 +- gemfiles/rails_4.gemfile | 2 +- gemfiles/rails_5.gemfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gemfiles/rails_3.gemfile b/gemfiles/rails_3.gemfile index 5c70608a9..7f6a46126 100644 --- a/gemfiles/rails_3.gemfile +++ b/gemfiles/rails_3.gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'rails', '3.2.19' +gem 'rails', '3.2.22.5' gem 'rack-cache', '<= 1.2' group :development, :test do diff --git a/gemfiles/rails_4.gemfile b/gemfiles/rails_4.gemfile index 7a4d248c1..8fdb1f659 100644 --- a/gemfiles/rails_4.gemfile +++ b/gemfiles/rails_4.gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'rails', '4.1.6' +gem 'rails', '4.2.10' group :development, :test do gem 'bundler' diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 2b59c1add..873aee2d0 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'rails', '5.0.0' +gem 'rails', '5.2.1' group :development, :test do gem 'bundler' From 9c942f4814b4e0e95bb721a59f97b8a4a37b2347 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Sat, 3 Nov 2018 08:22:34 +0800 Subject: [PATCH 040/290] Add ruby 2.5 support, drop 2.2. --- .travis.yml | 34 +++++++++++++++++----------------- CHANGELOG.md | 3 ++- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index d500a606d..68430a18f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,29 +4,37 @@ sudo: false matrix: include: - - rvm: 2.4.5 + - rvm: 2.5.3 script: - bundle exec danger - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: Gemfile - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: gemfiles/rack_1.5.2.gemfile - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: gemfiles/multi_json.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_json - - rvm: 2.4.5 + - rvm: 2.5.3 gemfile: gemfiles/multi_xml.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_xml + - rvm: 2.4.5 + gemfile: Gemfile + - rvm: 2.4.5 + gemfile: gemfiles/rack_edge.gemfile + - rvm: 2.4.5 + gemfile: gemfiles/rack_1.5.2.gemfile + - rvm: 2.4.5 + gemfile: gemfiles/rails_5.gemfile - rvm: 2.3.8 gemfile: Gemfile - rvm: 2.3.8 @@ -36,19 +44,11 @@ matrix: - rvm: 2.3.8 gemfile: gemfiles/rails_5.gemfile - rvm: 2.2.10 - gemfile: Gemfile - - rvm: 2.2.10 - gemfile: gemfiles/rack_1.5.2.gemfile - - rvm: 2.2.10 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.2.10 - gemfile: gemfiles/rails_4.gemfile - - rvm: 2.2.10 - gemfile: gemfiles/rails_3.gemfile - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 allow_failures: + - rvm: 2.2.10 - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 97513cf5e..2be86485d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ #### Features * Your contribution here. -* [#1795](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/bschmeck). +* [#1813](https://github.com/ruby-grape/grape/pull/1813): Add ruby 2.5 support, drop 2.2. Update rails version in travis - [@darren987469](https://github.com/darren987469). +* [#1803](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/bschmeck). * [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck). * [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469). From 022a642dcfd9f9bde7a01c42abcc3700085275c6 Mon Sep 17 00:00:00 2001 From: darren987469 Date: Sat, 3 Nov 2018 09:11:51 +0800 Subject: [PATCH 041/290] Update rack from 1.5.2 to 1.5.5 --- .travis.yml | 6 +++--- gemfiles/{rack_1.5.2.gemfile => rack_1.5.5.gemfile} | 2 +- spec/grape/integration/rack_spec.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename gemfiles/{rack_1.5.2.gemfile => rack_1.5.5.gemfile} (97%) diff --git a/.travis.yml b/.travis.yml index 68430a18f..1d56c6e90 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ matrix: - rvm: 2.5.3 gemfile: gemfiles/rack_edge.gemfile - rvm: 2.5.3 - gemfile: gemfiles/rack_1.5.2.gemfile + gemfile: gemfiles/rack_1.5.5.gemfile - rvm: 2.5.3 gemfile: gemfiles/rails_edge.gemfile - rvm: 2.5.3 @@ -32,7 +32,7 @@ matrix: - rvm: 2.4.5 gemfile: gemfiles/rack_edge.gemfile - rvm: 2.4.5 - gemfile: gemfiles/rack_1.5.2.gemfile + gemfile: gemfiles/rack_1.5.5.gemfile - rvm: 2.4.5 gemfile: gemfiles/rails_5.gemfile - rvm: 2.3.8 @@ -40,7 +40,7 @@ matrix: - rvm: 2.3.8 gemfile: gemfiles/rack_edge.gemfile - rvm: 2.3.8 - gemfile: gemfiles/rack_1.5.2.gemfile + gemfile: gemfiles/rack_1.5.5.gemfile - rvm: 2.3.8 gemfile: gemfiles/rails_5.gemfile - rvm: 2.2.10 diff --git a/gemfiles/rack_1.5.2.gemfile b/gemfiles/rack_1.5.5.gemfile similarity index 97% rename from gemfiles/rack_1.5.2.gemfile rename to gemfiles/rack_1.5.5.gemfile index a9b955f01..d7cdf495f 100644 --- a/gemfiles/rack_1.5.2.gemfile +++ b/gemfiles/rack_1.5.5.gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gem 'rack', '1.5.2' +gem 'rack', '1.5.5' group :development, :test do gem 'bundler' diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index e7c9f5665..e4b82a489 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -22,7 +22,7 @@ unless RUBY_PLATFORM == 'java' major, minor, patch = Rack.release.split('.').map(&:to_i) patch ||= 0 # rack <= 1.5.2 does not specify patch version - pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 3) || (minor >= 6))) + pending 'Rack 1.5.5 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 5) || (minor >= 6))) end expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test') From 513f857cd3940c53fd7630107c2c4e28b2ac45aa Mon Sep 17 00:00:00 2001 From: darren987469 Date: Fri, 2 Nov 2018 21:12:02 +0800 Subject: [PATCH 042/290] Add spec for default value in given block --- spec/grape/validations/params_scope_spec.rb | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index b561c0414..995fd0d0d 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -663,6 +663,32 @@ def initialize(value) end end + context 'default value in given block' do + before do + subject.params do + optional :a, values: %w[a b] + given a: ->(val) { val == 'a' } do + optional :b, default: 'default' + end + end + subject.get('/') { params.to_json } + end + + context 'when dependency meets' do + it 'sets default value for dependent parameter' do + get '/', a: 'a' + expect(last_response.body).to eq({ a: 'a', b: 'default' }.to_json) + end + end + + context 'when dependency does not meet' do + it 'does not set default value for dependent parameter' do + get '/', a: 'b' + expect(last_response.body).to eq({ a: 'b' }.to_json) + end + end + end + context 'when validations are dependent on a parameter within an array param' do before do subject.params do From d752a65d4583fd4ddc0e11fdae4c817e0d16e1c9 Mon Sep 17 00:00:00 2001 From: dm1try Date: Sun, 4 Nov 2018 12:56:53 +0300 Subject: [PATCH 043/290] update Appraisals according to the latest changes ref #1813 --- Appraisals | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Appraisals b/Appraisals index 8c7cd680c..ddc5e1d6e 100644 --- a/Appraisals +++ b/Appraisals @@ -1,18 +1,18 @@ appraise 'rails-3' do - gem 'rails', '3.2.19' + gem 'rails', '3.2.22.5' gem 'rack-cache', '<= 1.2' # Pin as next rack-cache version (1.3) removes Ruby1.9 support end appraise 'rails-4' do - gem 'rails', '4.1.6' + gem 'rails', '4.2.10' end appraise 'rails-5' do - gem 'rails', '5.0.0' + gem 'rails', '5.2.1' end -appraise 'rack-1.5.2' do - gem 'rack', '1.5.2' +appraise 'rack-1.5.5' do + gem 'rack', '1.5.5' end appraise 'rails-edge' do From 87b6243f05b7dcb32431ad74528986499501164e Mon Sep 17 00:00:00 2001 From: dm1try Date: Sun, 4 Nov 2018 12:57:52 +0300 Subject: [PATCH 044/290] Revert "Update rack from 1.5.2 to 1.5.5" This reverts commit 022a642dcfd9f9bde7a01c42abcc3700085275c6. rm useless appraisal ref #1813, #1066 --- .travis.yml | 6 ----- Appraisals | 4 ---- gemfiles/rack_1.5.5.gemfile | 35 ----------------------------- spec/grape/integration/rack_spec.rb | 2 +- 4 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 gemfiles/rack_1.5.5.gemfile diff --git a/.travis.yml b/.travis.yml index 1d56c6e90..aafc0bc1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ matrix: gemfile: Gemfile - rvm: 2.5.3 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.5.3 - gemfile: gemfiles/rack_1.5.5.gemfile - rvm: 2.5.3 gemfile: gemfiles/rails_edge.gemfile - rvm: 2.5.3 @@ -31,16 +29,12 @@ matrix: gemfile: Gemfile - rvm: 2.4.5 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.4.5 - gemfile: gemfiles/rack_1.5.5.gemfile - rvm: 2.4.5 gemfile: gemfiles/rails_5.gemfile - rvm: 2.3.8 gemfile: Gemfile - rvm: 2.3.8 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.3.8 - gemfile: gemfiles/rack_1.5.5.gemfile - rvm: 2.3.8 gemfile: gemfiles/rails_5.gemfile - rvm: 2.2.10 diff --git a/Appraisals b/Appraisals index ddc5e1d6e..c0d7415fd 100644 --- a/Appraisals +++ b/Appraisals @@ -11,10 +11,6 @@ appraise 'rails-5' do gem 'rails', '5.2.1' end -appraise 'rack-1.5.5' do - gem 'rack', '1.5.5' -end - appraise 'rails-edge' do gem 'rails', github: 'rails/rails' end diff --git a/gemfiles/rack_1.5.5.gemfile b/gemfiles/rack_1.5.5.gemfile deleted file mode 100644 index d7cdf495f..000000000 --- a/gemfiles/rack_1.5.5.gemfile +++ /dev/null @@ -1,35 +0,0 @@ -# This file was generated by Appraisal - -source 'https://rubygems.org' - -gem 'rack', '1.5.5' - -group :development, :test do - gem 'bundler' - gem 'hashie' - gem 'rake' - gem 'rubocop', '0.51.0' -end - -group :development do - gem 'appraisal' - gem 'benchmark-ips' - gem 'guard' - gem 'guard-rspec' - gem 'guard-rubocop' -end - -group :test do - gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false - gem 'danger-toc', '~> 0.1.2' - gem 'grape-entity', '~> 0.6' - gem 'maruku' - gem 'mime-types' - gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' - gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false -end - -gemspec path: '../' diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index e4b82a489..e7c9f5665 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -22,7 +22,7 @@ unless RUBY_PLATFORM == 'java' major, minor, patch = Rack.release.split('.').map(&:to_i) patch ||= 0 # rack <= 1.5.2 does not specify patch version - pending 'Rack 1.5.5 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 5) || (minor >= 6))) + pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 3) || (minor >= 6))) end expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test') From b14b196d15b70864524d1134f39dc1791a0a0567 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Tue, 13 Nov 2018 18:44:13 +0000 Subject: [PATCH 045/290] Fixes methods defined on included modules after remountable APIs (#1818) --- CHANGELOG.md | 2 +- lib/grape/api.rb | 16 +++++++++++++++- spec/grape/api_spec.rb | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2be86485d..bad29736f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * Your contribution here. * [#1813](https://github.com/ruby-grape/grape/pull/1813): Add ruby 2.5 support, drop 2.2. Update rails version in travis - [@darren987469](https://github.com/darren987469). -* [#1803](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/bschmeck). +* [#1803](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/myxoh). * [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck). * [#1791](https://github.com/ruby-grape/grape/pull/1791): Support `summary`, `hidden`, `deprecated`, `is_array`, `nickname`, `produces`, `consumes`, `tags` options in `desc` block - [@darren987469](https://github.com/darren987469). diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 63a636ddb..692e2fc96 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -7,7 +7,8 @@ module Grape class API # Class methods that we want to call on the API rather than on the API object NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? - respond_to? const_defined? const_missing parent parent_name name equal? to_s parents].freeze + respond_to? respond_to_missing? const_defined? const_missing parent + parent_name name equal? to_s parents].freeze class << self attr_accessor :base_instance, :instances @@ -81,6 +82,19 @@ def respond_to?(method, include_private = false) super(method, include_private) || base_instance.respond_to?(method, include_private) end + def respond_to_missing?(method, include_private = false) + base_instance.respond_to?(method, include_private) + end + + def method_missing(method, *args, &block) + # If there's a missing method, it may be defined on the base_instance instead. + if respond_to_missing?(method) + base_instance.send(method, *args, &block) + else + super + end + end + private # Adds a new stage to the set up require to get a Grape::API up and running diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 9ff4ea7de..3bbbbab22 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -3209,6 +3209,43 @@ def static expect { a.mount b }.to_not raise_error end end + + context 'when including a module' do + let(:included_module) do + Module.new do + def self.included(base) + base.extend(ClassMethods) + end + module ClassMethods + def my_method + @test = true + end + end + end + end + + it 'should correctly include module in nested mount' do + module_to_include = included_module + v1 = Class.new(Grape::API) do + version :v1, using: :path + include module_to_include + my_method + end + v2 = Class.new(Grape::API) do + version :v2, using: :path + end + segment_base = Class.new(Grape::API) do + mount v1 + mount v2 + end + + Class.new(Grape::API) do + mount segment_base + end + + expect(v1.my_method).to be_truthy + end + end end end From e8acc11a3b463f49a546cecb3f0bb7761b2d1ce1 Mon Sep 17 00:00:00 2001 From: Yoshida Nozomi Date: Thu, 22 Nov 2018 02:15:42 +0900 Subject: [PATCH 046/290] Raise validation error when optional Hash type parameter is received String type value and exactly_one_of be used (#1822) * adds test for exactly_one_of validator with nested and optional parmeters * add check if the parameter responds to any? method in MultipleParamsBase#scope_requires_params to handle nested invalid parameter --- .rubocop_todo.yml | 19 +++++----- CHANGELOG.md | 1 + .../validators/multiple_params_base.rb | 2 +- spec/grape/validations/params_scope_spec.rb | 36 +++++++++++++++++++ 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2c0b628a0..93c2e7046 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2017-12-10 01:15:22 +0900 using RuboCop version 0.51.0. +# on 2018-11-22 00:04:22 +0900 using RuboCop version 0.51.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -27,29 +27,30 @@ Lint/RescueWithoutErrorClass: Exclude: - 'lib/grape/validations/validators/coerce.rb' -# Offense count: 49 +# Offense count: 47 Metrics/AbcSize: Max: 44 -# Offense count: 282 +# Offense count: 4 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 3125 + Max: 182 # Offense count: 9 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 288 -# Offense count: 32 +# Offense count: 31 Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 1159 +# Offense count: 1227 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 215 + # Offense count: 57 # Configuration parameters: CountComments. Metrics/MethodLength: @@ -58,9 +59,7 @@ Metrics/MethodLength: # Offense count: 11 # Configuration parameters: CountComments. Metrics/ModuleLength: - Max: 212 - Exclude: - - 'spec/grape/dsl/routing_spec.rb' + Max: 220 # Offense count: 21 Metrics/PerceivedComplexity: @@ -91,7 +90,7 @@ Performance/HashEachMethods: - 'lib/grape/api/instance.rb' - 'lib/grape/middleware/versioner/header.rb' -# Offense count: 1 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. # SupportedStyles: line_count_based, semantic, braces_for_chaining diff --git a/CHANGELOG.md b/CHANGELOG.md index bad29736f..0ad6d4eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * [#1788](https://github.com/ruby-grape/grape/pull/1788): Fix route requirements bug - [@darren987469](https://github.com/darren987469), [@darrellnash](https://github.com/darrellnash). * [#1810](https://github.com/ruby-grape/grape/pull/1810): Fix support in `given` for aliased params - [@darren987469](https://github.com/darren987469). * [#1811](https://github.com/ruby-grape/grape/pull/1811): Support nested dependent parameters - [@darren987469](https://github.com/darren987469), [@andreacfm](https://github.com/andreacfm). +* [#1822](https://github.com/ruby-grape/grape/pull/1822): Raise validation error when optional hash type parameter is received string type value and exactly_one_of be used - [@woshidan](https://github.com/woshidan). ### 1.1.0 (8/4/2018) diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index 14209dff8..a8419661b 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -11,7 +11,7 @@ def validate!(params) private def scope_requires_params - @scope.required? || scoped_params.any?(&:any?) + @scope.required? || scoped_params.any? { |param| param.respond_to?(:any?) && param.any? } end def keys_in_common(resource_params) diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 995fd0d0d..10b21db61 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -1045,4 +1045,40 @@ def initialize(value) end end end + + context 'with exactly_one_of validation for optional parameters within an Hash param' do + before do + subject.params do + optional :memo, type: Hash do + optional :text, type: String + optional :custom_body, type: Hash, coerce_with: JSON + exactly_one_of :text, :custom_body + end + end + subject.get('test') + end + + context 'when correct data is provided' do + it 'returns a successful response' do + get 'test', memo: {} + expect(last_response.status).to eq(200) + + get 'test', memo: { text: 'HOGEHOGE' } + expect(last_response.status).to eq(200) + + get 'test', memo: { custom_body: '{ "xxx": "yyy" }' } + expect(last_response.status).to eq(200) + end + end + + context 'when invalid data is provided' do + it 'returns a failure response' do + get 'test', memo: { text: 'HOGEHOGE', custom_body: '{ "xxx": "yyy" }' } + expect(last_response.status).to eq(400) + + get 'test', memo: '{ "custom_body": "HOGE" }' + expect(last_response.status).to eq(400) + end + end + end end From c3338f9a1d4dd81ad2203ecc2cb5b825f7236bb6 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 23 Nov 2018 12:26:42 +0000 Subject: [PATCH 047/290] In order to properly use the autoloader, we do not want to delegate anonymous? to the instance --- lib/grape/api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 692e2fc96..aeac3caf9 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -8,7 +8,7 @@ class API # Class methods that we want to call on the API rather than on the API object NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? respond_to? respond_to_missing? const_defined? const_missing parent - parent_name name equal? to_s parents].freeze + parent_name name equal? to_s parents anonymous?].freeze class << self attr_accessor :base_instance, :instances From 7996660fb4cc88ee87ce716af28831d6e6b9af1c Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 23 Nov 2018 16:30:23 +0000 Subject: [PATCH 048/290] Modifies Upgrading.MD for opening/closing Grape Describes in more detail known impacts of the upgrade to `1.2.0` including what to do when opening / closing `Grape::API` --- UPGRADING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index b57dd6032..b5a9e68ea 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,6 +12,24 @@ with a class that can contain several instances of `Grape::API`. This changes were done in such a way that no code-changes should be required. However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`. +Note, this is particularly relevant if you are opening the class `Grape::API` for modification. This code: + +```ruby +class Grape::API + #your patched logic + ... +end +``` + +Needs to be modified into: + +```ruby +class Grape::API::Instance + #your patched logic + ... +end +``` + #### Changes in rescue_from returned object Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation. From c87944bf3d98e41ae02e02b9cb635f4a99f37726 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 23 Nov 2018 17:55:33 +0000 Subject: [PATCH 049/290] Update UPGRADING.md --- UPGRADING.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index b5a9e68ea..28318783c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -14,18 +14,17 @@ However, if experiencing problems, or relying on private methods and internal be Note, this is particularly relevant if you are opening the class `Grape::API` for modification. This code: +**Deprecated** ```ruby class Grape::API - #your patched logic + # your patched logic ... end ``` - -Needs to be modified into: - +**New** ```ruby class Grape::API::Instance - #your patched logic + # your patched logic ... end ``` From 8b00beee671e02f60d4974dfc440332b7d370fe6 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 23 Nov 2018 17:56:35 +0000 Subject: [PATCH 050/290] Update UPGRADING.md --- UPGRADING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADING.md b/UPGRADING.md index 28318783c..e2ed25f7c 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,7 +12,7 @@ with a class that can contain several instances of `Grape::API`. This changes were done in such a way that no code-changes should be required. However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`. -Note, this is particularly relevant if you are opening the class `Grape::API` for modification. This code: +Note, this is particularly relevant if you are opening the class `Grape::API` for modification. **Deprecated** ```ruby From 608a27d57bd85512d5c02a82b5deb401a6c17df6 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Mon, 26 Nov 2018 16:25:17 +0000 Subject: [PATCH 051/290] Preparing for release, 1.2.0. --- CHANGELOG.md | 12 +++++++++++- README.md | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ad6d4eab..b670a4f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -### 1.2.0 (Next) +### 1.2.1 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + +### 1.2.0 (29/11/2018) #### Features diff --git a/README.md b/README.md index e3fe7d36f..c4af69a86 100644 --- a/README.md +++ b/README.md @@ -147,9 +147,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.1.1**. +You're reading the documentation for the next release of Grape, which should be **1.2.0**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.1.0](https://github.com/ruby-grape/grape/blob/v1.1.0/README.md). +The current stable release is [1.2.0](https://github.com/ruby-grape/grape/blob/v1.2.0/README.md). ## Project Resources From 90ff3b6183ad5b49b7b6887dcf53fea4f6c4fab5 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Mon, 26 Nov 2018 16:27:09 +0000 Subject: [PATCH 052/290] Preparing for next development iteration, 1.2.1 --- README.md | 2 +- lib/grape/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c4af69a86..3bc0e4bbc 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.0**. +You're reading the documentation for the next release of Grape, which should be **1.2.1**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. The current stable release is [1.2.0](https://github.com/ruby-grape/grape/blob/v1.2.0/README.md). diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 9fce83d5f..c0b516511 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.2.0'.freeze + VERSION = '1.2.1'.freeze end From 26a65106226e2e09eea7a5490b377c76c9e0d1be Mon Sep 17 00:00:00 2001 From: Nico Date: Mon, 26 Nov 2018 16:44:53 +0000 Subject: [PATCH 053/290] Release 1.2.0 leftovers - remove redundant description --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b670a4f9d..863741b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ #### Features -* Your contribution here. * [#1813](https://github.com/ruby-grape/grape/pull/1813): Add ruby 2.5 support, drop 2.2. Update rails version in travis - [@darren987469](https://github.com/darren987469). * [#1803](https://github.com/ruby-grape/grape/pull/1803): Adds the ability to re-mount all endpoints in any location - [@myxoh](https://github.com/myxoh). * [#1795](https://github.com/ruby-grape/grape/pull/1795): Fix vendor/subtype parsing of an invalid Accept header - [@bschmeck](https://github.com/bschmeck). @@ -20,7 +19,6 @@ #### Fixes -* Your contribution here. * [#1796](https://github.com/ruby-grape/grape/pull/1796): Fix crash when available locales are enforced but fallback locale unavailable - [@Morred](https://github.com/Morred). * [#1776](https://github.com/ruby-grape/grape/pull/1776): Validate response returned by the exception handler - [@darren987469](https://github.com/darren987469). * [#1787](https://github.com/ruby-grape/grape/pull/1787): Add documented but not implemented ability to `.insert` a middleware in the stack - [@michaellennox](https://github.com/michaellennox). From de1f3e9ba47a6aeb45dc04ab5c435beeabadddbd Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.) @dblockdotorg" Date: Mon, 26 Nov 2018 14:22:16 -0500 Subject: [PATCH 054/290] Removed broken badge. [ci skip] --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 3bc0e4bbc..44bfe1966 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Gem Version](https://badge.fury.io/rb/grape.svg)](http://badge.fury.io/rb/grape) [![Build Status](https://travis-ci.org/ruby-grape/grape.svg?branch=master)](https://travis-ci.org/ruby-grape/grape) -[![Dependency Status](https://gemnasium.com/ruby-grape/grape.svg)](https://gemnasium.com/ruby-grape/grape) [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape) [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master) [![Inline docs](https://inch-ci.org/github/ruby-grape/grape.svg)](https://inch-ci.org/github/ruby-grape/grape) From f1908f4b5798e2b8ed889df518d9730093b690ff Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 28 Nov 2018 13:12:59 +0000 Subject: [PATCH 055/290] Exposes the name of the API via `to_s` (Addresses #1825) (#1826) --- CHANGELOG.md | 3 ++- UPGRADING.md | 34 ++++++++++++++++++++++++++++++++++ lib/grape/api/instance.rb | 4 ++++ spec/grape/named_api_spec.rb | 19 +++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 spec/grape/named_api_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 863741b58..59e675ea2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ #### Fixes * Your contribution here. +* [#1825](https://github.com/ruby-grape/grape/pull/1825): `to_s` on a mounted class now responses with the API name - [@myxoh](https://github.com/myxoh). -### 1.2.0 (29/11/2018) +### 1.2.0 (11/29/2018) #### Features diff --git a/UPGRADING.md b/UPGRADING.md index e2ed25f7c..65a829ec4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,10 +1,27 @@ Upgrading Grape =============== +### Upgrading to >= 1.2.1 + +#### Obtaining the name of a mounted class + +In order to make obtaining the name of a mounted class simpler, we've delegated `.to_s` to `base.name` + +**Deprecated in 1.2.0** +```ruby + payload[:endpoint].options[:for].name +``` +**New** +```ruby + payload[:endpoint].options[:for].to_s +``` + ### Upgrading to >= 1.2.0 #### Changes in the Grape::API class +##### Patching the class + In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced with a class that can contain several instances of `Grape::API`. @@ -29,6 +46,23 @@ class Grape::API::Instance end ``` +##### `name` (and other caveats) of the mounted API + +After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class +which inherit from `Grape::API::Instance`. +What this means in practice, is: +- Generally: you can access the named class from the instance calling the getter `base`. +- In particular: If you need the `name`, you can use `base`.`name` + +**Deprecated** +```ruby + payload[:endpoint].options[:for].name +``` +**New** +```ruby + payload[:endpoint].options[:for].base.name +``` + #### Changes in rescue_from returned object Grape will now check the object returned from `rescue_from` and ensure that it is a `Rack::Response`. That makes sure response is valid and avoids exposing service information. Change any code that invoked `Rack::Response.new(...).finish` in a custom `rescue_from` block to `Rack::Response.new(...)` to comply with the validation. diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index a3df4b083..dac787b0a 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -17,6 +17,10 @@ def base=(grape_api) grape_api.instances << self end + def to_s + (base && base.to_s) || super + end + # A class-level lock to ensure the API is not compiled by multiple # threads simultaneously within the same process. LOCK = Mutex.new diff --git a/spec/grape/named_api_spec.rb b/spec/grape/named_api_spec.rb new file mode 100644 index 000000000..c8b9ee501 --- /dev/null +++ b/spec/grape/named_api_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe 'A named API' do + subject(:api_name) { NamedAPI.endpoints.last.options[:for].to_s } + + let(:api) do + Class.new(Grape::API) do + get 'test' do + 'response' + end + end + end + + before { stub_const('NamedAPI', api) } + + it 'can access the name of the API' do + expect(api_name).to eq 'NamedAPI' + end +end From a0f158bd1872b809671455dfbcf1c0974c262c60 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 28 Nov 2018 18:15:13 +0000 Subject: [PATCH 056/290] Preparing for release, 1.2.1. --- CHANGELOG.md | 12 ++++++++++-- README.md | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59e675ea2..9880bf55e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.2.1 (Next) +### 1.2.2 (Next) #### Features @@ -7,9 +7,17 @@ #### Fixes * Your contribution here. + +### 1.2.1 (11/28/2018) + +#### Features + + +#### Fixes + * [#1825](https://github.com/ruby-grape/grape/pull/1825): `to_s` on a mounted class now responses with the API name - [@myxoh](https://github.com/myxoh). -### 1.2.0 (11/29/2018) +### 1.2.0 (11/26/2018) #### Features diff --git a/README.md b/README.md index 44bfe1966..805c6a566 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.1**. +You're reading the documentation for the next release of Grape, which should be **1.2.2**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.2.0](https://github.com/ruby-grape/grape/blob/v1.2.0/README.md). +The current stable release is [1.2.1](https://github.com/ruby-grape/grape/blob/v1.2.1/README.md). ## Project Resources From ee239873155e699820140c8c27361fd0fcae0c3a Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 28 Nov 2018 18:16:51 +0000 Subject: [PATCH 057/290] Preparing for the next development iteration, 1.2.2 --- lib/grape/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/version.rb b/lib/grape/version.rb index c0b516511..eda2b4384 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.2.1'.freeze + VERSION = '1.2.2'.freeze end From 9e78b7234bbbec68e764e77b2d163fb35f5ec123 Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 29 Nov 2018 11:06:23 +0000 Subject: [PATCH 058/290] Changs date format from MM/DD/YYYY to YYYY/MM/DD --- CHANGELOG.md | 88 ++++++++++++++++++++++++++-------------------------- RELEASING.md | 2 +- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9880bf55e..f821bec0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * Your contribution here. -### 1.2.1 (11/28/2018) +### 1.2.1 (2018/11/28) #### Features @@ -17,7 +17,7 @@ * [#1825](https://github.com/ruby-grape/grape/pull/1825): `to_s` on a mounted class now responses with the API name - [@myxoh](https://github.com/myxoh). -### 1.2.0 (11/26/2018) +### 1.2.0 (2018/11/26) #### Features @@ -36,7 +36,7 @@ * [#1811](https://github.com/ruby-grape/grape/pull/1811): Support nested dependent parameters - [@darren987469](https://github.com/darren987469), [@andreacfm](https://github.com/andreacfm). * [#1822](https://github.com/ruby-grape/grape/pull/1822): Raise validation error when optional hash type parameter is received string type value and exactly_one_of be used - [@woshidan](https://github.com/woshidan). -### 1.1.0 (8/4/2018) +### 1.1.0 (2018/8/4) #### Features @@ -50,7 +50,7 @@ * [#1765](https://github.com/ruby-grape/grape/pull/1765): Use 415 when request body is of an unsupported media type - [@jdmurphy](https://github.com/jdmurphy). * [#1771](https://github.com/ruby-grape/grape/pull/1771): Fix param aliases with 'given' blocks - [@jereynolds](https://github.com/jereynolds). -### 1.0.3 (4/23/2018) +### 1.0.3 (2018/4/23) #### Fixes @@ -63,7 +63,7 @@ * [#1754](https://github.com/ruby-grape/grape/pull/1754): Allow rescue from non-`StandardError` exceptions to use default error handling - [@jelkster](https://github.com/jelkster). * [#1756](https://github.com/ruby-grape/grape/pull/1756): Allow custom Grape exception handlers when the built-in exception handling is enabled - [@soylent](https://github.com/soylent). -### 1.0.2 (1/10/2018) +### 1.0.2 (2018/1/10) #### Features @@ -81,7 +81,7 @@ * [#1726](https://github.com/ruby-grape/grape/pull/1726): Improved startup performance during API method generation - [@jkowens](https://github.com/jkowens). * [#1727](https://github.com/ruby-grape/grape/pull/1727): Fix infinite loop when mounting endpoint with same superclass - [@jkowens](https://github.com/jkowens). -### 1.0.1 (9/8/2017) +### 1.0.1 (2017/9/8) #### Features @@ -95,7 +95,7 @@ * [#1661](https://github.com/ruby-grape/grape/pull/1661): Handle deeply-nested dependencies correctly - [@rnubel](https://github.com/rnubel), [@jnardone](https://github.com/jnardone). * [#1679](https://github.com/ruby-grape/grape/pull/1679): Treat StandardError from explicit values validator proc as false - [@jlfaber](https://github.com/jlfaber). -### 1.0.0 (7/3/2017) +### 1.0.0 (2017/7/3) #### Features @@ -114,7 +114,7 @@ * [#1625](https://github.com/ruby-grape/grape/pull/1625): Handle `given` correctly when nested in Array params - [@rnubel](https://github.com/rnubel), [@avellable](https://github.com/avellable). * [#1649](https://github.com/ruby-grape/grape/pull/1649): Don't share validator instances between requests - [@anakinj](https://github.com/anakinj). -### 0.19.2 (4/12/2017) +### 0.19.2 (2017/4/12) #### Features @@ -135,7 +135,7 @@ * [#1569](https://github.com/ruby-grape/grape/pull/1569), [#1511](https://github.com/ruby-grape/grape/issues/1511): Upgrade mustermann-grape to 1.0.0 - [@namusyaka](https://github.com/namusyaka). * [#1589](https://github.com/ruby-grape/grape/pull/1589): [#726](https://github.com/ruby-grape/grape/issues/726): Use default_format when Content-type is missing and respond with 406 when Content-type is invalid - [@inclooder](https://github.com/inclooder). -### 0.19.1 (1/9/2017) +### 0.19.1 (2017/1/9) #### Features @@ -147,7 +147,7 @@ * [#1548](https://github.com/ruby-grape/grape/pull/1548): Fix: avoid failing even if given path does not match with prefix - [@thomas-peyric](https://github.com/thomas-peyric), [@namusyaka](https://github.com/namusyaka). * [#1550](https://github.com/ruby-grape/grape/pull/1550): Fix: return 200 as default status for DELETE - [@jthornec](https://github.com/jthornec). -### 0.19.0 (12/18/2016) +### 0.19.0 (2016/12/18) #### Features @@ -163,7 +163,7 @@ * [#1510](https://github.com/ruby-grape/grape/pull/1510): Fix: inconsistent validation for multiple parameters - [@dgasper](https://github.com/dgasper). * [#1526](https://github.com/ruby-grape/grape/pull/1526): Reduced warnings caused by instance variables not initialized - [@cpetschnig](https://github.com/cpetschnig). -### 0.18.0 (10/7/2016) +### 0.18.0 (2016/10/7) #### Features @@ -180,7 +180,7 @@ * [#1488](https://github.com/ruby-grape/grape/pull/1488): Fix: ensure calling before filters when receiving OPTIONS request - [@namusyaka](https://github.com/namusyaka), [@jlfaber](https://github.com/jlfaber). * [#1493](https://github.com/ruby-grape/grape/pull/1493): Fix: coercion and lambda fails params validation - [@jonmchan](https://github.com/jonmchan). -### 0.17.0 (7/29/2016) +### 0.17.0 (2016/7/29) #### Features @@ -207,7 +207,7 @@ * [#1421](https://github.com/ruby-grape/grape/pull/1421): Avoid polluting `Grape::Middleware::Error` - [@namusyaka](https://github.com/namusyaka). * [#1422](https://github.com/ruby-grape/grape/pull/1422): Concat parent declared params with current one - [@plukevdh](https://github.com/plukevdh), [@rnubel](https://github.com/rnubel), [@namusyaka](https://github.com/namusyaka). -### 0.16.2 (4/12/2016) +### 0.16.2 (2016/4/12) #### Features @@ -220,7 +220,7 @@ * [#1359](https://github.com/ruby-grape/grape/pull/1359): Avoid evaluating the same route twice - [@namusyaka](https://github.com/namusyaka), [@dblock](https://github.com/dblock). * [#1361](https://github.com/ruby-grape/grape/pull/1361): Return 405 correctly even if version is using as header and wrong request method - [@namusyaka](https://github.com/namusyaka), [@dblock](https://github.com/dblock). -### 0.16.1 (4/3/2016) +### 0.16.1 (2016/4/3) #### Features @@ -235,7 +235,7 @@ * [#1330](https://github.com/ruby-grape/grape/pull/1330): Add `register` keyword for adding customized parsers and formatters - [@namusyaka](https://github.com/namusyaka). * [#1336](https://github.com/ruby-grape/grape/pull/1336): Do not modify Hash argument to `error!` - [@tjwp](https://github.com/tjwp). -### 0.15.0 (3/8/2016) +### 0.15.0 (2016/3/8) #### Features @@ -262,7 +262,7 @@ * [#1283](https://github.com/ruby-grape/grape/pull/1283): Fix 500 error for xml format when method is not allowed - [@304](https://github.com/304). * [#1197](https://github.com/ruby-grape/grape/pull/1290): Fix using JSON and Array[JSON] as groups when parameter is optional - [@lukeivers](https://github.com/lukeivers). -### 0.14.0 (12/07/2015) +### 0.14.0 (2015/12/07) #### Features @@ -289,7 +289,7 @@ * [#1101](https://github.com/ruby-grape/grape/pull/1101): Fix: Incorrect media-type `Accept` header now correctly returns 406 with `strict: true` - [@elliotlarson](https://github.com/elliotlarson). * [#1108](https://github.com/ruby-grape/grape/pull/1039): Raise a warning when `desc` is called with options hash and block - [@rngtng](https://github.com/rngtng). -### 0.13.0 (8/10/2015) +### 0.13.0 (2015/8/10) #### Features @@ -310,7 +310,7 @@ * [#1088](https://github.com/ruby-grape/grape/pull/1088): Support ActiveSupport 3.x by explicitly requiring `Hash#except` - [@wagenet](https://github.com/wagenet). * [#1096](https://github.com/ruby-grape/grape/pull/1096): Fix coercion on booleans - [@towanda](https://github.com/towanda). -### 0.12.0 (6/18/2015) +### 0.12.0 (2015/6/18) #### Features @@ -336,7 +336,7 @@ * [#1023](https://github.com/ruby-grape/grape/issues/1023): Fixes unexpected behavior with `present` and an object that responds to `merge` but isn't a Hash - [@dblock](https://github.com/dblock). * [#1017](https://github.com/ruby-grape/grape/pull/1017): Fixed `undefined method stringify_keys` with nested mutual exclusive params - [@quickpay](https://github.com/quickpay). -### 0.11.0 (2/23/2015) +### 0.11.0 (2015/2/23) * [#925](https://github.com/ruby-grape/grape/pull/925): Fixed `toplevel constant DateTime referenced by Virtus::Attribute::DateTime` - [@u2](https://github.com/u2). * [#916](https://github.com/ruby-grape/grape/pull/916): Added `DateTime/Date/Numeric/Boolean` type support `allow_blank` - [@u2](https://github.com/u2). @@ -353,12 +353,12 @@ * [#913](https://github.com/ruby-grape/grape/pull/913): Fix: Invalid accept headers cause internal processing errors (500) when http_codes are defined - [@croeck](https://github.com/croeck). * [#917](https://github.com/ruby-grape/grape/pull/917): Use HTTPS for rubygems.org - [@O-I](https://github.com/O-I). -### 0.10.1 (12/28/2014) +### 0.10.1 (2014/12/28) * [#868](https://github.com/ruby-grape/grape/pull/868), [#862](https://github.com/ruby-grape/grape/pull/862), [#861](https://github.com/ruby-grape/grape/pull/861): Fixed `version`, `prefix`, and other settings being overridden or changing scope when mounting API - [@yesmeck](https://github.com/yesmeck). * [#864](https://github.com/ruby-grape/grape/pull/864): Fixed `declared(params, include_missing: false)` now returning attributes with `nil` and `false` values - [@ppadron](https://github.com/ppadron). -### 0.10.0 (12/19/2014) +### 0.10.0 (2014/12/19) * [#803](https://github.com/ruby-grape/grape/pull/803), [#820](https://github.com/ruby-grape/grape/pull/820): Added `all_or_none_of` parameter validator - [@loveltyoic](https://github.com/loveltyoic), [@natecj](https://github.com/natecj). * [#774](https://github.com/ruby-grape/grape/pull/774): Extended `mutually_exclusive`, `exactly_one_of`, `at_least_one_of` to work inside any kind of group: `requires` or `optional`, `Hash` or `Array` - [@ShPakvel](https://github.com/ShPakvel). @@ -381,7 +381,7 @@ * [#679](https://github.com/ruby-grape/grape/issues/679): Fixed `OPTIONS` method returning 404 when combined with `prefix` - [@dblock](https://github.com/dblock). * [#679](https://github.com/ruby-grape/grape/issues/679): Fixed unsupported methods returning 404 instead of 405 when combined with `prefix` - [@dblock](https://github.com/dblock). -### 0.9.0 (8/27/2014) +### 0.9.0 (2014/8/27) #### Features @@ -399,7 +399,7 @@ * [#687](https://github.com/ruby-grape/grape/pull/687): Fix: `mutually_exclusive` and `exactly_one_of` validation error messages now label parameters as strings, consistently with `requires` and `optional` - [@dblock](https://github.com/dblock). -### 0.8.0 (7/10/2014) +### 0.8.0 (2014/7/10) #### Features @@ -419,7 +419,7 @@ * [#619](https://github.com/ruby-grape/grape/pull/619): Convert specs to RSpec 3 syntax with Transpec - [@danielspector](https://github.com/danielspector). * [#632](https://github.com/ruby-grape/grape/pull/632): `Grape::Endpoint#present` causes ActiveRecord to make an extra query during entity's detection - [@fixme](https://github.com/fixme). -### 0.7.0 (4/2/2014) +### 0.7.0 (2014/4/2) #### Features @@ -458,7 +458,7 @@ * [#587](https://github.com/ruby-grape/grape/pull/587): Fix oauth2 middleware compatibility with [draft-ietf-oauth-v2-31](http://tools.ietf.org/html/draft-ietf-oauth-v2-31) spec - [@etehtsea](https://github.com/etehtsea). * [#610](https://github.com/ruby-grape/grape/pull/610): Fixed group keyword was not working with type parameter - [@klausmeyer](https://github.com/klausmeyer). -### 0.6.1 (10/19/2013) +### 0.6.1 (2013/10/19) #### Features @@ -474,7 +474,7 @@ * Implemented Rubocop, a Ruby code static code analyzer - [@dblock](https://github.com/dblock). -### 0.6.0 (9/16/2013) +### 0.6.0 (2013/9/16) #### Features @@ -492,7 +492,7 @@ * [#428](https://github.com/ruby-grape/grape/issues/428): Removes memoization from `Grape::Request` params to prevent middleware from freezing parameter values before `Formatter` can get them - [@mbleigh](https://github.com/mbleigh). -### 0.5.0 (6/14/2013) +### 0.5.0 (2013/6/14) #### Features @@ -518,11 +518,11 @@ * [#423](https://github.com/ruby-grape/grape/pull/423): Fix: `Grape::Endpoint#declared` now correctly handles nested params (ie. declared with `group`) - [@jbarreneche](https://github.com/jbarreneche). * [#427](https://github.com/ruby-grape/grape/issues/427): Fix: `declared(params)` breaks when `params` contains array - [@timhabermaas](https://github.com/timhabermaas). -### 0.4.1 (4/1/2013) +### 0.4.1 (2013/4/1) * [#375](https://github.com/ruby-grape/grape/pull/375): Fix: throwing an `:error` inside a middleware doesn't respect the `format` settings - [@dblock](https://github.com/dblock). -### 0.4.0 (3/17/2013) +### 0.4.0 (2013/3/17) * [#356](https://github.com/ruby-grape/grape/pull/356): Fix: presenting collections other than `Array` (eg. `ActiveRecord::Relation`) - [@zimbatm](https://github.com/zimbatm). * [#352](https://github.com/ruby-grape/grape/pull/352): Fix: using `Rack::JSONP` with `Grape::Entity` responses - [@deckchair](https://github.com/deckchair). @@ -536,15 +536,15 @@ * [#353](https://github.com/ruby-grape/grape/issues/353): Revert to standard Ruby logger formatter, `require active_support/all` if you want old behavior - [@rhunter](https://github.com/rhunter), [@dblock](https://github.com/dblock). * Fix: `undefined method 'call' for nil:NilClass` for an API method implementation without a block, now returns an empty string - [@dblock](https://github.com/dblock). -### 0.3.2 (2/28/2013) +### 0.3.2 (2013/2/28) * [#355](https://github.com/ruby-grape/grape/issues/355): Relax dependency constraint on Hashie - [@reset](https://github.com/reset). -### 0.3.1 (2/25/2013) +### 0.3.1 (2013/2/25) * [#351](https://github.com/ruby-grape/grape/issues/351): Compatibility with Ruby 2.0 - [@mbleigh](https://github.com/mbleigh). -### 0.3.0 (02/21/2013) +### 0.3.0 (2013/02/21) * [#294](https://github.com/ruby-grape/grape/issues/294): Extracted `Grape::Entity` into a [grape-entity](https://github.com/agileanimal/grape-entity) gem - [@agileanimal](https://github.com/agileanimal). * [#340](https://github.com/ruby-grape/grape/pull/339), [#342](https://github.com/ruby-grape/grape/pull/342): Added `:cascade` option to `version` to allow disabling of rack/mount cascade behavior - [@dieb](https://github.com/dieb). @@ -563,12 +563,12 @@ * [#60](https://github.com/ruby-grape/grape/issues/60): Fix: mounting of a Grape API onto a path - [@dblock](https://github.com/dblock). * [#335](https://github.com/ruby-grape/grape/pull/335): Fix: request body parameters from a `PATCH` request not available in `params` - [@FreakenK](https://github.com/FreakenK). -### 0.2.6 (01/11/2013) +### 0.2.6 (2013/01/11) * Fix: support content-type with character set when parsing POST and PUT input - [@dblock](https://github.com/dblock). * Fix: CVE-2013-0175, multi_xml parse vulnerability, require multi_xml 0.5.2 - [@dblock](https://github.com/dblock). -### 0.2.5 (01/10/2013) +### 0.2.5 (2013/01/10) * Added support for custom parsers via `parser`, in addition to built-in multipart, JSON and XML parsers - [@dblock](https://github.com/dblock). * Removed `body_params`, data sent via a POST or PUT with a supported content-type is merged into `params` - [@dblock](https://github.com/dblock). @@ -577,7 +577,7 @@ * [#305](https://github.com/ruby-grape/grape/issues/305): Fix: presenting arrays of objects via `represent` or when auto-detecting an `Entity` constant in the objects being presented - [@brandonweiss](https://github.com/brandonweiss). * [#306](https://github.com/ruby-grape/grape/issues/306): Added i18n support for validation error messages - [@niedhui](https://github.com/niedhui). -### 0.2.4 (01/06/2013) +### 0.2.4 (2013/01/06) * [#297](https://github.com/ruby-grape/grape/issues/297): Added `default_error_formatter` - [@dblock](https://github.com/dblock). * [#297](https://github.com/ruby-grape/grape/issues/297): Setting `format` will automatically set `default_error_formatter` - [@dblock](https://github.com/dblock). @@ -595,7 +595,7 @@ * [#304](https://github.com/ruby-grape/grape/issues/304): Fix: `present x, :with => Entity` returns class references with `format :json` - [@dblock](https://github.com/dblock). * [#196](https://github.com/ruby-grape/grape/issues/196): Fix: root requests don't work with `prefix` - [@dblock](https://github.com/dblock). -### 0.2.3 (24/12/2012) +### 0.2.3 (2012/12/24) * [#179](https://github.com/ruby-grape/grape/issues/178): Using `content_type` will remove all default content-types - [@dblock](https://github.com/dblock). * [#265](https://github.com/ruby-grape/grape/issues/264): Fix: Moved `ValidationError` into `Grape::Exceptions` - [@thepumpkin1979](https://github.com/thepumpkin1979). @@ -608,7 +608,7 @@ * [#290](https://github.com/ruby-grape/grape/pull/290): The default error format for XML is now `error/message` instead of `hash/error` - [@dpsk](https://github.com/dpsk). * [#44](https://github.com/ruby-grape/grape/issues/44): Pass `env` into formatters to enable templating - [@dblock](https://github.com/dblock). -### 0.2.2 (12/10/2012) +### 0.2.2 (2012/12/10) #### Features @@ -630,7 +630,7 @@ * [#208](https://github.com/ruby-grape/grape/pull/208): `Entity#serializable_hash` must also check if attribute is generated by a user supplied block - [@ppadron](https://github.com/ppadron). * [#252](https://github.com/ruby-grape/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) - [@simulacre](https://github.com/simulacre). -### 0.2.1 (7/11/2012) +### 0.2.1 (2012/7/11) * [#186](https://github.com/ruby-grape/grape/issues/186): Fix: helpers allow multiple calls with modules and blocks - [@ppadron](https://github.com/ppadron). * [#188](https://github.com/ruby-grape/grape/pull/188): Fix: multi-method routes append '(.:format)' only once - [@kainosnoema](https://github.com/kainosnoema). @@ -645,7 +645,7 @@ * [#189](https://github.com/ruby-grape/grape/pull/189): `HEAD` requests no longer return a body - [@stephencelis](https://github.com/stephencelis). * [#97](https://github.com/ruby-grape/grape/issues/97): Allow overriding `Content-Type` - [@dblock](https://github.com/dblock). -### 0.2.0 (3/28/2012) +### 0.2.0 (2012/3/28) * Added support for inheriting exposures from entities - [@bobbytables](https://github.com/bobbytables). * Extended formatting with `default_format` - [@dblock](https://github.com/dblock). @@ -665,28 +665,28 @@ * Added support for before and after filters - [@mbleigh](https://github.com/mbleigh). * Extended `rescue_from`, which can now take a block - [@dblock](https://github.com/dblock). -### 0.1.5 (6/14/2011) +### 0.1.5 (2011/6/14) * Extended exception handling to all exceptions - [@dblock](https://github.com/dblock). * Added support for returning JSON objects from within error blocks - [@dblock](https://github.com/dblock). * Added support for handling incoming JSON in body - [@tedkulp](https://github.com/tedkulp). * Added support for HTTP digest authentication - [@daddz](https://github.com/daddz). -### 0.1.4 (4/8/2011) +### 0.1.4 (2011/4/8) * Allow multiple definitions of the same endpoint under multiple versions - [@chrisrhoden](https://github.com/chrisrhoden). * Added support for multipart URL parameters - [@mcastilho](https://github.com/mcastilho). * Added support for custom formatters - [@spraints](https://github.com/spraints). -### 0.1.3 (1/10/2011) +### 0.1.3 (2011/1/10) * Added support for JSON format in route matching - [@aiwilliams](https://github.com/aiwilliams). * Added suport for custom middleware - [@mbleigh](https://github.com/mbleigh). -### 0.1.1 (11/14/2010) +### 0.1.1 (2010/11/14) * Endpoints properly reset between each request - [@mbleigh](https://github.com/mbleigh). -### 0.1.0 (11/13/2010) +### 0.1.0 (2010/11/13) * Initial public release - [@mbleigh](https://github.com/mbleigh). diff --git a/RELEASING.md b/RELEASING.md index 4a5a7209e..22540fa59 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -36,7 +36,7 @@ You're reading the documentation for the stable release of Grape, 0.6.0. Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. ``` -#### 0.6.0 (9/16/2013) +#### 0.6.0 (2013/9/16) ``` Remove the line with "Your contribution here.", since there will be no more contributions to this release. From 532c08d6f3386ae63a5a31c16f281f4d0ba2bbd3 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 30 Nov 2018 12:01:35 +0000 Subject: [PATCH 059/290] restores self_sanity --- lib/grape/api.rb | 10 +++++++--- spec/grape/api_spec.rb | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index aeac3caf9..740fba4ea 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,12 +6,16 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = %I[define_singleton_method instance_variable_set inspect class is_a? ! kind_of? - respond_to? respond_to_missing? const_defined? const_missing parent - parent_name name equal? to_s parents anonymous?].freeze + NON_OVERRIDABLE = Class.new.methods.freeze class << self attr_accessor :base_instance, :instances + + # Rather than initializing an object of type Grape::API, create an object of type Instance + def new(*args, &block) + base_instance.new(*args, &block) + end + # When inherited, will create a list of all instances (times the API was mounted) # It will listen to the setup required to mount that endpoint, and replicate it on any new instance def inherited(api, base_instance_parent = Grape::API::Instance) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 3bbbbab22..a6d287b71 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -3649,4 +3649,22 @@ def before end end end + + describe 'normal class methods' do + subject(:grape_api) { Class.new(Grape::API) } + + before do + stub_const('MyAPI', grape_api) + end + + it 'can find the appropiate name' do + expect(grape_api.name).to eq 'MyAPI' + end + + it 'is equal to itself' do + expect(grape_api.itself).to eq grape_api + expect(grape_api).to eq MyAPI + expect(grape_api.eql?(MyAPI)) + end + end end From 19852e26e118b09d73429d5497c270e888c113a7 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 30 Nov 2018 13:02:15 +0000 Subject: [PATCH 060/290] Adds the change to CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9880bf55e..a1ba3ed08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#1830](https://github.com/ruby-grape/grape/pull/1830): Restores self_sanity addresses #1829 - [@myxoh](https://github.com/myxoh). ### 1.2.1 (11/28/2018) From 96a6e1ccd795a187207e5491ba98f191a33ccfdf Mon Sep 17 00:00:00 2001 From: Fotos Georgiadis Date: Sun, 2 Dec 2018 04:14:48 +0100 Subject: [PATCH 061/290] Move summary documentation under options hash --- lib/grape/dsl/desc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index 1fe128127..a3a7839c6 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -4,12 +4,12 @@ module Desc include Grape::DSL::Settings # Add a description to the next namespace or function. - # @option options :summary [String] summary for this endpoint # @param description [String] descriptive string for this endpoint # or namespace # @param options [Hash] other properties you can set to describe the # endpoint or namespace. Optional. # @option options :detail [String] additional detail about this endpoint + # @option options :summary [String] summary for this endpoint # @option options :params [Hash] param types and info. normally, you set # these via the `params` dsl method. # @option options :entity [Grape::Entity] the entity returned upon a From 43ef591799902e0ba27d86a0d436caf953ffbe2c Mon Sep 17 00:00:00 2001 From: Fotos Georgiadis Date: Mon, 3 Dec 2018 06:06:24 +0100 Subject: [PATCH 062/290] Format headers in Grape::DSL::Desc spec --- spec/grape/dsl/desc_spec.rb | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/spec/grape/dsl/desc_spec.rb b/spec/grape/dsl/desc_spec.rb index bd9f61c5f..7a0778c5c 100644 --- a/spec/grape/dsl/desc_spec.rb +++ b/spec/grape/dsl/desc_spec.rb @@ -28,14 +28,16 @@ class Dummy entity: Object, http_codes: [[401, 'Unauthorized', 'Entities::Error']], named: 'My named route', - headers: [XAuthToken: { - description: 'Valdates your identity', - required: true - }, - XOptionalHeader: { - description: 'Not really needed', - required: false - }], + headers: [ + XAuthToken: { + description: 'Valdates your identity', + required: true + }, + XOptionalHeader: { + description: 'Not really needed', + required: false + } + ], hidden: false, deprecated: false, is_array: true, @@ -52,14 +54,16 @@ class Dummy success Object failure [[401, 'Unauthorized', 'Entities::Error']] named 'My named route' - headers [XAuthToken: { - description: 'Valdates your identity', - required: true - }, - XOptionalHeader: { - description: 'Not really needed', - required: false - }] + headers [ + XAuthToken: { + description: 'Valdates your identity', + required: true + }, + XOptionalHeader: { + description: 'Not really needed', + required: false + } + ] hidden false deprecated false is_array true From 3aad58e0e2c6f3aad0cd5122c96da85768fcde9e Mon Sep 17 00:00:00 2001 From: Fotos Georgiadis Date: Sun, 2 Dec 2018 04:21:02 +0100 Subject: [PATCH 063/290] Add security option in desc block --- lib/grape/dsl/desc.rb | 2 ++ spec/grape/dsl/desc_spec.rb | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index a3a7839c6..ac2798a04 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -24,6 +24,7 @@ module Desc # @option options :nickname [String] nickname of the endpoint # @option options :produces [Array[String]] a list of MIME types the endpoint produce # @option options :consumes [Array[String]] a list of MIME types the endpoint consume + # @option options :security [Array[Hash]] a list of security schemes # @option options :tags [Array[String]] a list of tags # @yield a block yielding an instance context with methods mapping to # each of the above, except that :entity is also aliased as #success @@ -100,6 +101,7 @@ def desc_container :nickname, :produces, :consumes, + :security, :tags ) diff --git a/spec/grape/dsl/desc_spec.rb b/spec/grape/dsl/desc_spec.rb index 7a0778c5c..6fb2e69a9 100644 --- a/spec/grape/dsl/desc_spec.rb +++ b/spec/grape/dsl/desc_spec.rb @@ -44,7 +44,8 @@ class Dummy nickname: 'nickname', produces: %w[array of mime_types], consumes: %w[array of mime_types], - tags: %w[tag1 tag2] + tags: %w[tag1 tag2], + security: %w[array of security schemes] } subject.desc 'The description' do @@ -71,6 +72,7 @@ class Dummy produces %w[array of mime_types] consumes %w[array of mime_types] tags %w[tag1 tag2] + security %w[array of security schemes] end expect(subject.namespace_setting(:description)).to eq(expected_options) From 05df7c56b88e26d58c51901e23746f51f50e8f2c Mon Sep 17 00:00:00 2001 From: Fotos Georgiadis Date: Mon, 3 Dec 2018 06:17:48 +0100 Subject: [PATCH 064/290] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cac588e..ba2b5db02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#1831](https://github.com/ruby-grape/grape/pull/1831): Support security in desc block - [@fotos](https://github.com/fotos). * [#1830](https://github.com/ruby-grape/grape/pull/1830): Restores self_sanity addresses #1829 - [@myxoh](https://github.com/myxoh). ### 1.2.1 (2018/11/28) From 933750fc8b72e418720c83f6a7be466d600866d8 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.) @dblockdotorg" Date: Mon, 3 Dec 2018 09:44:08 -0500 Subject: [PATCH 065/290] Fixup formatting and fix vs. feature. [ci skip] --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba2b5db02..84afb8282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,13 @@ #### Features +* [#1831](https://github.com/ruby-grape/grape/pull/1831): Support `security` in `desc` block - [@fotos](https://github.com/fotos). * Your contribution here. #### Fixes * Your contribution here. -* [#1831](https://github.com/ruby-grape/grape/pull/1831): Support security in desc block - [@fotos](https://github.com/fotos). -* [#1830](https://github.com/ruby-grape/grape/pull/1830): Restores self_sanity addresses #1829 - [@myxoh](https://github.com/myxoh). +* [#1830](https://github.com/ruby-grape/grape/pull/1830), [#1829](https://github.com/ruby-grape/grape/issues/1829): Restores `self` sanity - [@myxoh](https://github.com/myxoh). ### 1.2.1 (2018/11/28) From 7413b7ea4a1ab323d354f7199488016125bdeee1 Mon Sep 17 00:00:00 2001 From: Fotos Georgiadis Date: Wed, 5 Dec 2018 15:25:12 +0100 Subject: [PATCH 066/290] Support body_name in desc block (#1832) --- CHANGELOG.md | 3 ++- lib/grape/dsl/desc.rb | 2 ++ spec/grape/dsl/desc_spec.rb | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84afb8282..9e42a83e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ #### Features -* [#1831](https://github.com/ruby-grape/grape/pull/1831): Support `security` in `desc` block - [@fotos](https://github.com/fotos). * Your contribution here. +* [#1832](https://github.com/ruby-grape/grape/pull/1832): Support `body_name` in `desc` block - [@fotos](https://github.com/fotos). +* [#1831](https://github.com/ruby-grape/grape/pull/1831): Support `security` in `desc` block - [@fotos](https://github.com/fotos). #### Fixes diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index ac2798a04..2b63cf184 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -17,6 +17,7 @@ module Desc # @option options :http_codes [Array[Array]] possible HTTP codes this # endpoint may return, with their meanings, in a 2d array # @option options :named [String] a specific name to help find this route + # @option options :body_name [String] override the autogenerated body name param # @option options :headers [Hash] HTTP headers this method can accept # @option options :hidden [Boolean] hide the endpoint or not # @option options :deprecated [Boolean] deprecate the endpoint or not @@ -94,6 +95,7 @@ def desc_container :entity, :http_codes, :named, + :body_name, :headers, :hidden, :deprecated, diff --git a/spec/grape/dsl/desc_spec.rb b/spec/grape/dsl/desc_spec.rb index 6fb2e69a9..52ba42660 100644 --- a/spec/grape/dsl/desc_spec.rb +++ b/spec/grape/dsl/desc_spec.rb @@ -28,6 +28,7 @@ class Dummy entity: Object, http_codes: [[401, 'Unauthorized', 'Entities::Error']], named: 'My named route', + body_name: 'My body name', headers: [ XAuthToken: { description: 'Valdates your identity', @@ -55,6 +56,7 @@ class Dummy success Object failure [[401, 'Unauthorized', 'Entities::Error']] named 'My named route' + body_name 'My body name' headers [ XAuthToken: { description: 'Valdates your identity', From aabb72219c3babd8c78a8c0c73739f0ceae1e58c Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 6 Dec 2018 23:28:01 +0000 Subject: [PATCH 067/290] Addresses memory issues related to new multi-instance approach (#1836) --- CHANGELOG.md | 1 + lib/grape/api.rb | 11 ++++++++++- spec/grape/api_spec.rb | 9 +++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e42a83e8..1367557b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Fixes * Your contribution here. +* [#1836](https://github.com/ruby-grape/grape/pull/1836): Fix: memory leak not releasing `call` method calls from setup - [@myxoh](https://github.com/myxoh). * [#1830](https://github.com/ruby-grape/grape/pull/1830), [#1829](https://github.com/ruby-grape/grape/issues/1829): Restores `self` sanity - [@myxoh](https://github.com/myxoh). ### 1.2.1 (2018/11/28) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 740fba4ea..b943ff3a7 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = Class.new.methods.freeze + NON_OVERRIDABLE = Class.new.methods.freeze + %i[call call!] class << self attr_accessor :base_instance, :instances @@ -42,6 +42,15 @@ def override_all_methods! end end + # This is the interface point between Rack and Grape; it accepts a request + # from Rack and ultimately returns an array of three values: the status, + # the headers, and the body. See [the rack specification] + # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. + # NOTE: This will only be called on an API directly mounted on RACK + def call(*args, &block) + base_instance.call(*args, &block) + end + # Allows an API to itself be inheritable: def make_inheritable(api) # When a child API inherits from a parent API. diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index a6d287b71..6397dbfc4 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -220,6 +220,15 @@ def app end end + describe '.call' do + context 'it does not add to the app setup' do + it 'calls the app' do + expect(subject).not_to receive(:add_setup) + subject.call({}) + end + end + end + describe '.route_param' do it 'adds a parameterized route segment namespace' do subject.namespace :users do From e76398bbdc82f4f59d1f26bc5e12c2d065859067 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:00:36 -0500 Subject: [PATCH 068/290] Freeze the result of the call. --- lib/grape/api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index b943ff3a7..918c07131 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = Class.new.methods.freeze + %i[call call!] + NON_OVERRIDABLE = (Class.new.methods + %i[call call!]).freeze class << self attr_accessor :base_instance, :instances From 36eca406f9f442bc89621b61810200ff56170b74 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:09:14 -0500 Subject: [PATCH 069/290] Fix: use parenthesis in the argument to avoid ambiguity. --- lib/grape/middleware/stack.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/middleware/stack.rb b/lib/grape/middleware/stack.rb index 7f3550c47..abfbdaa45 100644 --- a/lib/grape/middleware/stack.rb +++ b/lib/grape/middleware/stack.rb @@ -96,7 +96,7 @@ def build(builder = Rack::Builder.new) # @param [Array] other_specs An array of middleware specifications (e.g. [[:use, klass], [:insert_before, *args]]) def concat(other_specs) @others << Array(other_specs).reject { |o| o.first == :use } - merge_with Array(other_specs).select { |o| o.first == :use } + merge_with(Array(other_specs).select { |o| o.first == :use }) end protected From 662bbc68a5aa62d488e31d3c09b18e64c8b41abe Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:09:39 -0500 Subject: [PATCH 070/290] Fix: use Array.include? instead of multiple comparisons. --- lib/grape/validations/types/custom_type_coercer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index 8e3ca2194..f6b26cf83 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -158,7 +158,7 @@ def infer_type_check(type) # necessary. def enforce_symbolized_keys(type, method) # Collections have all values processed individually - if type == Array || type == Set + if [Array, Set].include?(type) lambda do |val| method.call(val).tap do |new_value| new_value.map do |item| From e929d1ce15087f1c433f7fef825074fbacc853e5 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:10:03 -0500 Subject: [PATCH 071/290] Fix: use blocks instead of ambiguous { }. --- .rubocop_todo.yml | 26 ++----------------------- spec/grape/middleware/formatter_spec.rb | 8 ++++---- spec/grape/validations_spec.rb | 2 +- 3 files changed, 7 insertions(+), 29 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 93c2e7046..c11897703 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-11-22 00:04:22 +0900 using RuboCop version 0.51.0. +# on 2018-12-06 21:06:59 -0500 using RuboCop version 0.51.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -16,10 +16,9 @@ Layout/IndentHeredoc: - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' -# Offense count: 2 +# Offense count: 1 Lint/AmbiguousBlockAssociation: Exclude: - - 'lib/grape/middleware/stack.rb' - 'spec/grape/dsl/routing_spec.rb' # Offense count: 1 @@ -90,22 +89,6 @@ Performance/HashEachMethods: - 'lib/grape/api/instance.rb' - 'lib/grape/middleware/versioner/header.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. -# SupportedStyles: line_count_based, semantic, braces_for_chaining -# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object -# FunctionalMethods: let, let!, subject, watch -# IgnoredMethods: lambda, proc, it -Style/BlockDelimiters: - Exclude: - - 'spec/grape/middleware/formatter_spec.rb' - -# Offense count: 1 -Style/CommentedKeyword: - Exclude: - - 'spec/grape/validations_spec.rb' - # Offense count: 2 # Configuration parameters: SupportedStyles. # SupportedStyles: annotated, template @@ -121,8 +104,3 @@ Style/IdenticalConditionalBranches: Style/MethodMissing: Exclude: - 'lib/grape/router/attribute_translator.rb' - -# Offense count: 1 -Style/MultipleComparison: - Exclude: - - 'lib/grape/validations/types/custom_type_coercer.rb' diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index aaa0488ac..a4b46acdf 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -247,7 +247,7 @@ def to_xml let(:content_type) { 'application/atom+xml' } it 'returns a 415 HTTP error status' do - error = catch(:error) { + error = catch(:error) do subject.call( 'PATH_INFO' => '/info', 'REQUEST_METHOD' => method, @@ -255,7 +255,7 @@ def to_xml 'rack.input' => io, 'CONTENT_LENGTH' => io.length ) - } + end expect(error[:status]).to eq(415) expect(error[:message]).to eq("The provided content-type 'application/atom+xml' is not supported.") end @@ -407,7 +407,7 @@ def self.call(_, _) parsers: { json: ->(_object, _env) { raise StandardError, 'fail' } } ) io = StringIO.new('{invalid}') - error = catch(:error) { + error = catch(:error) do subject.call( 'PATH_INFO' => '/info', 'REQUEST_METHOD' => 'POST', @@ -415,7 +415,7 @@ def self.call(_, _) 'rack.input' => io, 'CONTENT_LENGTH' => io.length ) - } + end expect(error[:message]).to eq 'fail' expect(error[:backtrace].size).to be >= 1 diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index a5b79be9e..90e04bafe 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -1013,7 +1013,7 @@ def validate_param!(attr_name, params) expect(last_response.body).to eq('custom is not custom with options!') end end - end # end custom validation + end context 'named' do context 'can be defined' do From d9c972c36997f39b6d47c653b7b82eb639884c3d Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:12:20 -0500 Subject: [PATCH 072/290] Fix: Use meaningful heredoc delimiters. --- .rubocop_todo.yml | 7 ------- lib/grape/router/route.rb | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c11897703..567c86afe 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -74,13 +74,6 @@ Naming/FileName: - 'Guardfile' - 'Rakefile' -# Offense count: 1 -# Configuration parameters: Blacklist. -# Blacklist: END, (?-mix:EO[A-Z]{1}) -Naming/HeredocDelimiterNaming: - Exclude: - - 'lib/grape/router/route.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect. diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index bf6e47b88..52e0098b3 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -98,9 +98,9 @@ def warn_route_methods(name, location, expected = nil) path, line = *location.scan(SOURCE_LOCATION_REGEXP).first path = File.realpath(path) if Pathname.new(path).relative? expected ||= name - warn <<-EOS + warn <<-WARNING #{path}:#{line}: The route_xxx methods such as route_#{name} have been deprecated, please use #{expected}. - EOS + WARNING end end end From 80c8ff6c3b27eb37fc41ecef4a04666c27659479 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:14:21 -0500 Subject: [PATCH 073/290] Fix: use each_key and each_value. --- .rubocop_todo.yml | 8 -------- lib/grape/api/instance.rb | 2 +- lib/grape/middleware/versioner/header.rb | 4 ++-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 567c86afe..33b8fe763 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -74,14 +74,6 @@ Naming/FileName: - 'Guardfile' - 'Rakefile' -# Offense count: 3 -# Cop supports --auto-correct. -# Configuration parameters: AutoCorrect. -Performance/HashEachMethods: - Exclude: - - 'lib/grape/api/instance.rb' - - 'lib/grape/middleware/versioner/header.rb' - # Offense count: 2 # Configuration parameters: SupportedStyles. # SupportedStyles: annotated, template diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index dac787b0a..2fdb91fa1 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -183,7 +183,7 @@ def add_head_not_allowed_methods_and_options_methods # informations again. without_root_prefix do without_versioning do - routes_map.each do |_, config| + routes_map.each_value do |config| methods = config[:methods] allowed_methods = methods.dup diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index b2cd806df..f90bc37f0 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -99,7 +99,7 @@ def fail_with_invalid_version_header!(message) def available_media_types available_media_types = [] - content_types.each do |extension, _media_type| + content_types.each_key do |extension| versions.reverse_each do |version| available_media_types += [ "application/vnd.#{vendor}-#{version}+#{extension}", @@ -111,7 +111,7 @@ def available_media_types available_media_types << "application/vnd.#{vendor}" - content_types.each do |_, media_type| + content_types.each_value do |media_type| available_media_types << media_type end From 654ee689cfe31c268b2f22d97d5f6801b213bc6d Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:16:53 -0500 Subject: [PATCH 074/290] Fix: remove duplicate code and unwrap conditional. --- .rubocop_todo.yml | 5 ----- lib/grape/dsl/desc.rb | 7 +++---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 33b8fe763..ca2d46a23 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -80,11 +80,6 @@ Naming/FileName: Style/FormatStringToken: EnforcedStyle: template -# Offense count: 2 -Style/IdenticalConditionalBranches: - Exclude: - - 'lib/grape/dsl/desc.rb' - # Offense count: 1 Style/MethodMissing: Exclude: diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index 2b63cf184..d13a451ee 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -69,13 +69,12 @@ def desc(description, options = {}, &config_block) end def description_field(field, value = nil) + description = route_setting(:description) if value - description = route_setting(:description) description ||= route_setting(:description, {}) description[field] = value - else - description = route_setting(:description) - description[field] if description + elsif description + description[field] end end From 99d5ec738ecffb0ecc918346b3fec2a4af34a2f1 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 6 Dec 2018 21:18:11 -0500 Subject: [PATCH 075/290] Fix: fallback on super. --- .rubocop_todo.yml | 5 ----- lib/grape/router/attribute_translator.rb | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index ca2d46a23..4c81012cd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -79,8 +79,3 @@ Naming/FileName: # SupportedStyles: annotated, template Style/FormatStringToken: EnforcedStyle: template - -# Offense count: 1 -Style/MethodMissing: - Exclude: - - 'lib/grape/router/attribute_translator.rb' diff --git a/lib/grape/router/attribute_translator.rb b/lib/grape/router/attribute_translator.rb index bea51d1f9..8614fa66e 100644 --- a/lib/grape/router/attribute_translator.rb +++ b/lib/grape/router/attribute_translator.rb @@ -15,6 +15,8 @@ def method_missing(m, *args) @attributes[m[0..-1]] = *args elsif m[-1] != '=' @attributes[m] + else + super end end From c20a73ac1e3f3ba1082005ed61bf69452373ba87 Mon Sep 17 00:00:00 2001 From: dblock Date: Fri, 7 Dec 2018 10:57:12 -0500 Subject: [PATCH 076/290] Preparing for release, 1.2.2. --- CHANGELOG.md | 7 +------ README.md | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1367557b8..a6d654af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,17 @@ -### 1.2.2 (Next) +### 1.2.2 (2018/12/07) #### Features -* Your contribution here. * [#1832](https://github.com/ruby-grape/grape/pull/1832): Support `body_name` in `desc` block - [@fotos](https://github.com/fotos). * [#1831](https://github.com/ruby-grape/grape/pull/1831): Support `security` in `desc` block - [@fotos](https://github.com/fotos). #### Fixes -* Your contribution here. * [#1836](https://github.com/ruby-grape/grape/pull/1836): Fix: memory leak not releasing `call` method calls from setup - [@myxoh](https://github.com/myxoh). * [#1830](https://github.com/ruby-grape/grape/pull/1830), [#1829](https://github.com/ruby-grape/grape/issues/1829): Restores `self` sanity - [@myxoh](https://github.com/myxoh). ### 1.2.1 (2018/11/28) -#### Features - - #### Fixes * [#1825](https://github.com/ruby-grape/grape/pull/1825): `to_s` on a mounted class now responses with the API name - [@myxoh](https://github.com/myxoh). diff --git a/README.md b/README.md index 805c6a566..77952e738 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,8 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.2**. +You're reading the documentation for the stable release of Grape, **1.2.2**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.2.1](https://github.com/ruby-grape/grape/blob/v1.2.1/README.md). ## Project Resources From e1a0879922175b4eba21accc9d998209b8f6de89 Mon Sep 17 00:00:00 2001 From: dblock Date: Fri, 7 Dec 2018 10:58:19 -0500 Subject: [PATCH 077/290] Preparing for next developer iteration, 1.2.3. --- CHANGELOG.md | 10 ++++++++++ README.md | 3 ++- lib/grape/version.rb | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d654af7..062a9643a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.2.3 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.2.2 (2018/12/07) #### Features diff --git a/README.md b/README.md index 77952e738..0038234dd 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, **1.2.2**. +You're reading the documentation for the next release of Grape, which should be **1.2.3**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.2.2](https://github.com/ruby-grape/grape/blob/v1.2.2/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index eda2b4384..34954ca45 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.2.2'.freeze + VERSION = '1.2.3'.freeze end From b4f4d2a92c0943d1eb75833f2159c7ec38153b35 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 10 Dec 2018 13:32:13 -0500 Subject: [PATCH 078/290] Use coveralls-reborn for rails-edge. --- Gemfile | 2 +- gemfiles/multi_json.gemfile | 2 +- gemfiles/multi_xml.gemfile | 2 +- gemfiles/rack_edge.gemfile | 2 +- gemfiles/rails_3.gemfile | 2 +- gemfiles/rails_4.gemfile | 2 +- gemfiles/rails_5.gemfile | 2 +- gemfiles/rails_edge.gemfile | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 9ea7a5058..2881582d8 100644 --- a/Gemfile +++ b/Gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index af5797e5c..4ab020140 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index b323389cf..c093022a6 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index ee2b96774..eb276ec2b 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/rails_3.gemfile b/gemfiles/rails_3.gemfile index 7f6a46126..c6445ee6a 100644 --- a/gemfiles/rails_3.gemfile +++ b/gemfiles/rails_3.gemfile @@ -22,7 +22,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/rails_4.gemfile b/gemfiles/rails_4.gemfile index 8fdb1f659..2f331cb86 100644 --- a/gemfiles/rails_4.gemfile +++ b/gemfiles/rails_4.gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 873aee2d0..c8c38fbb4 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index bce0e1942..56ecdf5f0 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -21,7 +21,7 @@ end group :test do gem 'cookiejar' - gem 'coveralls', '~> 0.8.17', require: false + gem 'coveralls_reborn' gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' From 766cdb5872ee934518b544175aca7d24d5746f71 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Mon, 10 Dec 2018 20:34:02 +0000 Subject: [PATCH 079/290] Allows to set a global ParamBuilder (#1833) --- CHANGELOG.md | 1 + README.md | 30 ++++++++++++++++++++++++++++-- lib/grape.rb | 2 +- lib/grape/config.rb | 32 ++++++++++++++++++++++++++++++++ lib/grape/request.rb | 2 +- spec/grape/config_spec.rb | 17 +++++++++++++++++ spec/grape/request_spec.rb | 24 ++++++++++++++++++++++++ 7 files changed, 104 insertions(+), 4 deletions(-) create mode 100644 lib/grape/config.rb create mode 100644 spec/grape/config_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 062a9643a..575ebb641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1833](https://github.com/ruby-grape/grape/pull/1833): Allows to set the `ParamBuilder` globally - [@myxoh](https://github.com/myxoh). #### Fixes diff --git a/README.md b/README.md index 0038234dd..6d0d6721a 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,14 @@ - [Rails](#rails) - [Modules](#modules) - [Remounting](#remounting) - - [Configuration](#configuration) + - [Mount Configuration](#mount-configuration) - [Versioning](#versioning) - [Path](#path) - [Header](#header) - [Accept-Version Header](#accept-version-header) - [Param](#param) - [Describing Methods](#describing-methods) +- [Configuration](#configuration) - [Parameters](#parameters) - [Params Class](#params-class) - [Declared](#declared) @@ -395,7 +396,7 @@ end Assuming that the post and comment endpoints are mounted in `/posts` and `/comments`, you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes`. -### Configuration +### Mount Configuration You can configure remountable endpoints for small details changing according to where they are mounted. @@ -541,6 +542,29 @@ end [grape-swagger]: https://github.com/ruby-grape/grape-swagger +## Configuration + +Use `Grape.configure` to set up global settings at load time. +Currently the configurable settings are: + +* `param_builder`: Sets the [Parameter Builder](#parameters), defaults to `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder`. + +To change a setting value make sure that at some point on load time the code the following code runs + +```ruby +Grape.configure do |config| + config.setting = value +end +``` + +For example, for the `param_builder`, the following code could run in an initializers: + +```ruby +Grape.configure do |config| + config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder +end +``` + ## Parameters Request parameters are available through the `params` hash object. This includes `GET`, `POST` @@ -618,6 +642,8 @@ params do end ``` +Or globally with the [Configuration](#configuration) `Grape.configure.param_builder`. + In the example above, `params["color"]` will return `nil` since `params` is a plain `Hash`. Available parameter builders are `Grape::Extensions::Hash::ParamBuilder`, `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` and `Grape::Extensions::Hashie::Mash::ParamBuilder`. diff --git a/lib/grape.rb b/lib/grape.rb index d626ec5c0..90597562d 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -34,7 +34,6 @@ module Grape autoload :Namespace autoload :Path - autoload :Cookies autoload :Validations autoload :ErrorFormatter @@ -194,6 +193,7 @@ module ServeFile end end +require 'grape/config' require 'grape/util/content_types' require 'grape/validations/validators/base' diff --git a/lib/grape/config.rb b/lib/grape/config.rb new file mode 100644 index 000000000..a1ebb706a --- /dev/null +++ b/lib/grape/config.rb @@ -0,0 +1,32 @@ +module Grape + module Config + class Configuration + ATTRIBUTES = %i[ + param_builder + ].freeze + + attr_accessor(*ATTRIBUTES) + + def initialize + reset + end + + def reset + self.param_builder = Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder + end + end + + def self.extended(base) + def base.configure + block_given? ? yield(config) : config + end + + def base.config + @configuration ||= Grape::Config::Configuration.new + end + end + end +end + +Grape.extend Grape::Config +Grape.config.reset diff --git a/lib/grape/request.rb b/lib/grape/request.rb index 0f11dff9e..7209a60d0 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -5,7 +5,7 @@ class Request < Rack::Request alias rack_params params def initialize(env, options = {}) - extend options[:build_params_with] || Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder + extend options[:build_params_with] || Grape.config.param_builder super(env) end diff --git a/spec/grape/config_spec.rb b/spec/grape/config_spec.rb new file mode 100644 index 000000000..f9290e3c7 --- /dev/null +++ b/spec/grape/config_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe '.configure' do + before do + Grape.configure do |config| + config.param_builder = 42 + end + end + + after do + Grape.config.reset + end + + it 'is configured to the new value' do + expect(Grape.config.param_builder).to eq 42 + end +end diff --git a/spec/grape/request_spec.rb b/spec/grape/request_spec.rb index b763532ca..2c352d175 100644 --- a/spec/grape/request_spec.rb +++ b/spec/grape/request_spec.rb @@ -62,6 +62,30 @@ module Grape end end + describe 'when the param_builder is set to Hashie' do + before do + Grape.configure do |config| + config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder + end + end + + after do + Grape.config.reset + end + + subject(:request_params) { Grape::Request.new(env, opts).params } + + context 'when the API does not include a specific param builder' do + let(:opts) { {} } + it { is_expected.to be_a(Hashie::Mash) } + end + + context 'when the API includes a specific param builder' do + let(:opts) { { build_params_with: Grape::Extensions::Hash::ParamBuilder } } + it { is_expected.to be_a(Hash) } + end + end + describe '#headers' do let(:options) do default_options.merge(request_headers) From d307fa114e6642c331f04f5038482a907a49e639 Mon Sep 17 00:00:00 2001 From: asakawa Date: Thu, 13 Dec 2018 11:27:58 +0800 Subject: [PATCH 080/290] fix: enforce tempfile to be a Tempfile object --- CHANGELOG.md | 1 + lib/grape/validations/types/file.rb | 2 +- spec/grape/validations/validators/coerce_spec.rb | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 575ebb641..540f7495d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Your contribution here. * [#1833](https://github.com/ruby-grape/grape/pull/1833): Allows to set the `ParamBuilder` globally - [@myxoh](https://github.com/myxoh). +* [#1844](https://github.com/ruby-grape/grape/pull/1844): Fix: enforce `:tempfile` to be a `Tempfile` object in `File` validator - [@Nyangawa](https://github.com/Nyangawa). #### Fixes diff --git a/lib/grape/validations/types/file.rb b/lib/grape/validations/types/file.rb index fe1aedc32..62aa3f694 100644 --- a/lib/grape/validations/types/file.rb +++ b/lib/grape/validations/types/file.rb @@ -19,7 +19,7 @@ def value_coerced?(value) # Rack::Request creates a Hash with filename, # content type and an IO object. Do a bit of basic # duck-typing. - value.is_a?(::Hash) && value.key?(:tempfile) + value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile) end end end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 34406c389..bf4a5cc8a 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -397,6 +397,10 @@ def self.parsed?(value) post '/upload', file: 'not a file' expect(last_response.status).to eq(400) expect(last_response.body).to eq('file is invalid') + + post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' } + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('file is invalid') end it 'Nests integers' do From 2efbd0116b3ef69cdafdd5edba9412ed91fdfe63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Glauco=20Cust=C3=B3dio?= Date: Mon, 17 Dec 2018 12:27:25 +0000 Subject: [PATCH 081/290] Add same_as validator --- CHANGELOG.md | 1 + README.md | 22 +++++++ lib/grape.rb | 1 + lib/grape/locale/en.yml | 1 + lib/grape/validations/validators/same_as.rb | 23 +++++++ .../validations/validators/same_as_spec.rb | 63 +++++++++++++++++++ spec/spec_helper.rb | 1 + 7 files changed, 112 insertions(+) create mode 100644 lib/grape/validations/validators/same_as.rb create mode 100644 spec/grape/validations/validators/same_as_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 540f7495d..ffbdac6ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1850](https://github.com/ruby-grape/grape/pull/1850): Adds `same_as` validator - [@glaucocustodio](https://github.com/glaucocustodio). * [#1833](https://github.com/ruby-grape/grape/pull/1833): Allows to set the `ParamBuilder` globally - [@myxoh](https://github.com/myxoh). * [#1844](https://github.com/ruby-grape/grape/pull/1844): Fix: enforce `:tempfile` to be a `Tempfile` object in `File` validator - [@Nyangawa](https://github.com/Nyangawa). diff --git a/README.md b/README.md index 6d0d6721a..317df4e19 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ - [allow_blank](#allow_blank) - [values](#values) - [except_values](#except_values) + - [same_as](#same_as) - [regexp](#regexp) - [mutually_exclusive](#mutually_exclusive) - [exactly_one_of](#exactly_one_of) @@ -62,6 +63,7 @@ - [I18n](#i18n) - [Custom Validation messages](#custom-validation-messages) - [presence, allow_blank, values, regexp](#presence-allow_blank-values-regexp) + - [same_as](#same_as-1) - [all_or_none_of](#all_or_none_of-1) - [mutually_exclusive](#mutually_exclusive-1) - [exactly_one_of](#exactly_one_of-1) @@ -1351,6 +1353,17 @@ params do end ``` +#### `same_as` + +A `same_as` option can be given to ensure that values of parameters match. + +```ruby +params do + requires :password + requires :password_confirmation, same_as: :password +end +``` + #### `regexp` Parameters can be restricted to match a specific regular expression with the `:regexp` option. If the value @@ -1663,6 +1676,15 @@ params do end ``` +#### `same_as` + +```ruby +params do + requires :password + requires :password_confirmation, same_as: { value: :password, message: 'not match' } +end +``` + #### `all_or_none_of` ```ruby diff --git a/lib/grape.rb b/lib/grape.rb index 90597562d..9cfcfceca 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -207,6 +207,7 @@ module ServeFile require 'grape/validations/validators/mutual_exclusion' require 'grape/validations/validators/presence' require 'grape/validations/validators/regexp' +require 'grape/validations/validators/same_as' require 'grape/validations/validators/values' require 'grape/validations/validators/except_values' require 'grape/validations/params_scope' diff --git a/lib/grape/locale/en.yml b/lib/grape/locale/en.yml index 7ebfa97fc..10fa381f7 100644 --- a/lib/grape/locale/en.yml +++ b/lib/grape/locale/en.yml @@ -9,6 +9,7 @@ en: blank: 'is empty' values: 'does not have a valid value' except_values: 'has a value not allowed' + same_as: 'is not the same as %{parameter}' missing_vendor_option: problem: 'missing :vendor option.' summary: 'when version using header, you must specify :vendor option. ' diff --git a/lib/grape/validations/validators/same_as.rb b/lib/grape/validations/validators/same_as.rb new file mode 100644 index 000000000..126ba3f4d --- /dev/null +++ b/lib/grape/validations/validators/same_as.rb @@ -0,0 +1,23 @@ +module Grape + module Validations + class SameAsValidator < Base + def validate_param!(attr_name, params) + confirmation = options_key?(:value) ? @option[:value] : @option + return if params[attr_name] == params[confirmation] + raise Grape::Exceptions::Validation, + params: [@scope.full_name(attr_name)], + message: build_message + end + + private + + def build_message + if options_key?(:message) + @option[:message] + else + format I18n.t(:same_as, scope: 'grape.errors.messages'), parameter: @option + end + end + end + end +end diff --git a/spec/grape/validations/validators/same_as_spec.rb b/spec/grape/validations/validators/same_as_spec.rb new file mode 100644 index 000000000..37f10fc45 --- /dev/null +++ b/spec/grape/validations/validators/same_as_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe Grape::Validations::SameAsValidator do + module ValidationsSpec + module SameAsValidatorSpec + class API < Grape::API + params do + requires :password + requires :password_confirmation, same_as: :password + end + post do + end + + params do + requires :password + requires :password_confirmation, same_as: { value: :password, message: 'not match' } + end + post '/custom-message' do + end + end + end + end + + def app + ValidationsSpec::SameAsValidatorSpec::API + end + + describe '/' do + context 'is the same' do + it do + post '/', password: '987654', password_confirmation: '987654' + expect(last_response.status).to eq(201) + expect(last_response.body).to eq('') + end + end + + context 'is not the same' do + it do + post '/', password: '123456', password_confirmation: 'whatever' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('password_confirmation is not the same as password') + end + end + end + + describe '/custom-message' do + context 'is the same' do + it do + post '/custom-message', password: '987654', password_confirmation: '987654' + expect(last_response.status).to eq(201) + expect(last_response.body).to eq('') + end + end + + context 'is not the same' do + it do + post '/custom-message', password: '123456', password_confirmation: 'whatever' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('password_confirmation not match') + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9baf9bf57..1653756ad 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,7 @@ config.include Rack::Test::Methods config.include Spec::Support::Helpers config.raise_errors_for_deprecations! + config.filter_run_when_matching :focus config.before(:each) { Grape::Util::InheritableSetting.reset_global! } end From b645fb48940e5f60e2581013108e329702e2182b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Glauco=20Cust=C3=B3dio?= Date: Fri, 21 Dec 2018 16:46:01 +0000 Subject: [PATCH 082/290] fix: validators checking aliased param instead of original one (#1852) --- .rubocop_todo.yml | 8 ++++---- CHANGELOG.md | 4 ++-- lib/grape/endpoint.rb | 10 +++++++++- lib/grape/validations/validators/as.rb | 1 - spec/grape/validations/params_scope_spec.rb | 10 ++++++++++ 5 files changed, 25 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4c81012cd..627a5a51a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-12-06 21:06:59 -0500 using RuboCop version 0.51.0. +# on 2018-12-20 21:38:24 +0000 using RuboCop version 0.51.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -38,13 +38,13 @@ Metrics/BlockLength: # Offense count: 9 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 288 + Max: 295 # Offense count: 31 Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 1227 +# Offense count: 1234 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: @@ -55,7 +55,7 @@ Metrics/LineLength: Metrics/MethodLength: Max: 33 -# Offense count: 11 +# Offense count: 12 # Configuration parameters: CountComments. Metrics/ModuleLength: Max: 220 diff --git a/CHANGELOG.md b/CHANGELOG.md index ffbdac6ed..6fcf57a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,11 @@ * Your contribution here. * [#1850](https://github.com/ruby-grape/grape/pull/1850): Adds `same_as` validator - [@glaucocustodio](https://github.com/glaucocustodio). * [#1833](https://github.com/ruby-grape/grape/pull/1833): Allows to set the `ParamBuilder` globally - [@myxoh](https://github.com/myxoh). -* [#1844](https://github.com/ruby-grape/grape/pull/1844): Fix: enforce `:tempfile` to be a `Tempfile` object in `File` validator - [@Nyangawa](https://github.com/Nyangawa). #### Fixes -* Your contribution here. +* [#1852](https://github.com/ruby-grape/grape/pull/1852): `allow_blank` called after `as` when the original param is not blank - [@glaucocustodio](https://github.com/glaucocustodio). +* [#1844](https://github.com/ruby-grape/grape/pull/1844): Enforce `:tempfile` to be a `Tempfile` object in `File` validator - [@Nyangawa](https://github.com/Nyangawa). ### 1.2.2 (2018/12/07) diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 5100f9654..650c53203 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -258,6 +258,7 @@ def run else run_filters before_validations, :before_validation run_validators validations, request + remove_aliased_params run_filters after_validations, :after_validation response_object = @block ? @block.call(self) : nil end @@ -319,7 +320,14 @@ def build_helpers Module.new { helpers.each { |mod_to_include| include mod_to_include } } end - private :build_stack, :build_helpers + def remove_aliased_params + return unless route_setting(:aliased_params) + route_setting(:aliased_params).flat_map(&:keys).each do |aliased_param| + @params.delete(aliased_param) + end + end + + private :build_stack, :build_helpers, :remove_aliased_params def helpers lazy_initialize! && @helpers diff --git a/lib/grape/validations/validators/as.rb b/lib/grape/validations/validators/as.rb index d1840539b..7547c8edd 100644 --- a/lib/grape/validations/validators/as.rb +++ b/lib/grape/validations/validators/as.rb @@ -8,7 +8,6 @@ def initialize(attrs, options, required, scope, opts = {}) def validate_param!(attr_name, params) params[@alias] = params[attr_name] - params.delete(attr_name) end end end diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 10b21db61..12865b8bc 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -155,6 +155,16 @@ def initialize(value) expect(last_response.status).to eq(400) expect(last_response.body).to eq('foo is empty') end + + it do + subject.params do + requires :foo, as: :bar, allow_blank: false + end + subject.get('/alias-not-blank-with-value') {} + get '/alias-not-blank-with-value', foo: 'any' + + expect(last_response.status).to eq(200) + end end context 'array without coerce type explicitly given' do From 663e88b7703d1a9e358d224bf63622a6d9a58ee3 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 16 Jan 2019 17:44:59 +0000 Subject: [PATCH 083/290] Preparing for release, 1.2.3. --- CHANGELOG.md | 11 ++++++++++- README.md | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fcf57a14..8521e058d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,17 @@ -### 1.2.3 (Next) +### 1.2.4 (Next) #### Features * Your contribution here. + +#### Fixes + +* Your contribution here. + +### 1.2.3 (2019/01/16) + +#### Features + * [#1850](https://github.com/ruby-grape/grape/pull/1850): Adds `same_as` validator - [@glaucocustodio](https://github.com/glaucocustodio). * [#1833](https://github.com/ruby-grape/grape/pull/1833): Allows to set the `ParamBuilder` globally - [@myxoh](https://github.com/myxoh). diff --git a/README.md b/README.md index 317df4e19..527994c3a 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.3**. +You're reading the documentation for the next release of Grape, which should be **1.2.4**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.2.2](https://github.com/ruby-grape/grape/blob/v1.2.2/README.md). +The current stable release is [1.2.3](https://github.com/ruby-grape/grape/blob/v1.2.3/README.md). ## Project Resources From 4c209284c701b3c1aafae67a5c7452f8c6879647 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 16 Jan 2019 17:54:26 +0000 Subject: [PATCH 084/290] Preparing for next iteration: 1.2.3 --- lib/grape/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 34954ca45..8b97336b7 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.2.3'.freeze + VERSION = '1.2.4'.freeze end From d6fcf853947be5d38d4b2df8434f8ed3dc0cd06b Mon Sep 17 00:00:00 2001 From: Hemu Arumugam Date: Tue, 29 Jan 2019 16:36:08 -0800 Subject: [PATCH 085/290] Minor cleanup to README.md in a few places --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 527994c3a..c8727e3d4 100644 --- a/README.md +++ b/README.md @@ -400,7 +400,7 @@ Assuming that the post and comment endpoints are mounted in `/posts` and `/comme ### Mount Configuration -You can configure remountable endpoints for small details changing according to where they are mounted. +You can configure remountable endpoints to change small details according to where they are mounted. ```ruby class Voting::API < Grape::API @@ -551,7 +551,7 @@ Currently the configurable settings are: * `param_builder`: Sets the [Parameter Builder](#parameters), defaults to `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder`. -To change a setting value make sure that at some point on load time the code the following code runs +To change a setting value make sure that at some point during load time the following code runs ```ruby Grape.configure do |config| @@ -559,7 +559,7 @@ Grape.configure do |config| end ``` -For example, for the `param_builder`, the following code could run in an initializers: +For example, for the `param_builder`, the following code could run in an initializer: ```ruby Grape.configure do |config| From fd95befcab90fdab51d92de5cc95dca4c45874d9 Mon Sep 17 00:00:00 2001 From: dm1try Date: Thu, 31 Jan 2019 12:51:55 +0300 Subject: [PATCH 086/290] CI, relax rules for "rack_edge" builds test only a single job and allow failures --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index aafc0bc1e..1626ab6f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,10 @@ language: ruby sudo: false +# "gemfile" is required for "allow_failures" option, +# see https://docs.travis-ci.com/user/customizing-the-build/#matching-jobs-with-allow_failures +gemfile: + matrix: include: - rvm: 2.5.3 @@ -27,14 +31,10 @@ matrix: - bundle exec rspec spec/integration/multi_xml - rvm: 2.4.5 gemfile: Gemfile - - rvm: 2.4.5 - gemfile: gemfiles/rack_edge.gemfile - rvm: 2.4.5 gemfile: gemfiles/rails_5.gemfile - rvm: 2.3.8 gemfile: Gemfile - - rvm: 2.3.8 - gemfile: gemfiles/rack_edge.gemfile - rvm: 2.3.8 gemfile: gemfiles/rails_5.gemfile - rvm: 2.2.10 @@ -46,5 +46,7 @@ matrix: - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 + - rvm: 2.5.3 + gemfile: gemfiles/rack_edge.gemfile bundler_args: --without development From 0588ad06573ef9c199f169f72c29e70a65878aa7 Mon Sep 17 00:00:00 2001 From: Atul Bhosale Date: Mon, 25 Feb 2019 22:21:29 +0530 Subject: [PATCH 087/290] Update copyright notice to 2019 [ci skip] --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index c0b20e593..05d951bc4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2018 Michael Bleigh, Intridea Inc. and Contributors. +Copyright (c) 2010-2019 Michael Bleigh, Intridea Inc. and Contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index c8727e3d4..4d234c742 100644 --- a/README.md +++ b/README.md @@ -3645,4 +3645,4 @@ MIT License. See LICENSE for details. ## Copyright -Copyright (c) 2010-2018 Michael Bleigh, Intridea Inc. and Contributors. +Copyright (c) 2010-2019 Michael Bleigh, Intridea Inc. and Contributors. From b79bd9c899908a92a751c54e36c133bb9273ff7a Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Tue, 26 Feb 2019 20:08:34 +0000 Subject: [PATCH 088/290] Allows for a block of code to always run after the endpoint (#1864) Closes #1865 --- .rubocop_todo.yml | 4 +- CHANGELOG.md | 1 + README.md | 21 +++- lib/grape/dsl/callbacks.rb | 20 ++++ lib/grape/endpoint.rb | 54 ++++++---- lib/grape/middleware/error.rb | 3 +- spec/grape/api_spec.rb | 193 ++++++++++++++++++++++++++++++++++ spec/grape/endpoint_spec.rb | 6 ++ 8 files changed, 270 insertions(+), 32 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 627a5a51a..cf7b6377e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -38,7 +38,7 @@ Metrics/BlockLength: # Offense count: 9 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 295 + Max: 302 # Offense count: 31 Metrics/CyclomaticComplexity: @@ -53,7 +53,7 @@ Metrics/LineLength: # Offense count: 57 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 33 + Max: 34 # Offense count: 12 # Configuration parameters: CountComments. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8521e058d..30fa5ee59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh). #### Fixes diff --git a/README.md b/README.md index 4d234c742..95a55a08a 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ - [Register custom middleware for authentication](#register-custom-middleware-for-authentication) - [Describing and Inspecting an API](#describing-and-inspecting-an-api) - [Current Route and Endpoint](#current-route-and-endpoint) -- [Before and After](#before-and-after) +- [Before, After and Finally](#before-after-and-finally) - [Anchoring](#anchoring) - [Using Custom Middleware](#using-custom-middleware) - [Grape Middleware](#grape-middleware) @@ -3089,19 +3089,22 @@ class ApiLogger < Grape::Middleware::Base end ``` -## Before and After +## Before, After and Finally Blocks can be executed before or after every API call, using `before`, `after`, `before_validation` and `after_validation`. +If the API fails the `after` call will not be trigered, if you need code to execute for sure +use the `finally`. Before and after callbacks execute in the following order: 1. `before` 2. `before_validation` 3. _validations_ -4. `after_validation` -5. _the API call_ -6. `after` +4. `after_validation` (upon successful validation) +5. _the API call_ (upon successful validation) +6. `after` (upon successful validation and API call) +7. `finally` (always) Steps 4, 5 and 6 only happen if validation succeeds. @@ -3121,6 +3124,14 @@ before do end ``` +You can ensure a block of code runs after every request (including failures) with `finally`: + +```ruby +finally do + # this code will run after every request (successful or failed) +end +``` + **Namespaces** Callbacks apply to each API call within and below the current namespace: diff --git a/lib/grape/dsl/callbacks.rb b/lib/grape/dsl/callbacks.rb index ede063c16..a74144995 100644 --- a/lib/grape/dsl/callbacks.rb +++ b/lib/grape/dsl/callbacks.rb @@ -43,6 +43,26 @@ def after_validation(&block) def after(&block) namespace_stackable(:afters, block) end + + # Allows you to specify a something that will always be executed after a call + # API call. Unlike the `after` block, this code will run even on + # unsuccesful requests. + # @example + # class ExampleAPI < Grape::API + # before do + # ApiLogger.start + # end + # finally do + # ApiLogger.close + # end + # end + # + # This will make sure that the ApiLogger is opened and close around every + # request + # @param ensured_block [Proc] The block to be executed after every api_call + def finally(&block) + namespace_stackable(:finallies, block) + end end end end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 650c53203..b1ae819d4 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -245,33 +245,37 @@ def run @request = Grape::Request.new(env, build_params_with: namespace_inheritable(:build_params_with)) @params = @request.params @headers = @request.headers + begin + cookies.read(@request) + self.class.run_before_each(self) + run_filters befores, :before + + if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS]) + raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options? + header 'Allow', allowed_methods + response_object = '' + status 204 + else + run_filters before_validations, :before_validation + run_validators validations, request + remove_aliased_params + run_filters after_validations, :after_validation + response_object = @block ? @block.call(self) : nil + end - cookies.read(@request) - self.class.run_before_each(self) - run_filters befores, :before - - if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS]) - raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options? - header 'Allow', allowed_methods - response_object = '' - status 204 - else - run_filters before_validations, :before_validation - run_validators validations, request - remove_aliased_params - run_filters after_validations, :after_validation - response_object = @block ? @block.call(self) : nil - end + run_filters afters, :after + cookies.write(header) - run_filters afters, :after - cookies.write(header) + # status verifies body presence when DELETE + @body ||= response_object - # status verifies body presence when DELETE - @body ||= response_object + # The body commonly is an Array of Strings, the application instance itself, or a File-like object + response_object = file || [body] - # The Body commonly is an Array of Strings, the application instance itself, or a File-like object - response_object = file || [body] - [status, header, response_object] + [status, header, response_object] + ensure + run_filters finallies, :finally + end end end @@ -392,6 +396,10 @@ def afters namespace_stackable(:afters) || [] end + def finallies + namespace_stackable(:finallies) || [] + end + def validations route_setting(:saved_validations) || [] end diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 05ea790ce..e7d6da7e3 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -21,7 +21,7 @@ def default_options }, rescue_handlers: {}, # rescue handler blocks base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class - all_rescue_handler: nil # rescue handler block to rescue from all exceptions + all_rescue_handler: nil, # rescue handler block to rescue from all exceptions } end @@ -32,7 +32,6 @@ def initialize(app, **options) def call!(env) @env = env - begin error_response(catch(:error) do return @app.call(@env) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 6397dbfc4..310c43dd6 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1628,6 +1628,199 @@ def three end end + describe 'lifecycle' do + let!(:lifecycle) { [] } + let!(:standard_cycle) do + %i[before before_validation after_validation api_call after finally] + end + + let!(:validation_error) do + %i[before before_validation finally] + end + + let!(:errored_cycle) do + %i[before before_validation after_validation api_call finally] + end + + before do + current_cycle = lifecycle + + subject.before do + current_cycle << :before + end + + subject.before_validation do + current_cycle << :before_validation + end + + subject.after_validation do + current_cycle << :after_validation + end + + subject.after do + current_cycle << :after + end + + subject.finally do + current_cycle << :finally + end + end + + context 'when the api_call succeeds' do + before do + current_cycle = lifecycle + + subject.get 'api_call' do + current_cycle << :api_call + end + end + + it 'follows the standard life_cycle' do + get '/api_call' + expect(lifecycle).to eq standard_cycle + end + end + + context 'when the api_call has a controlled error' do + before do + current_cycle = lifecycle + + subject.get 'api_call' do + current_cycle << :api_call + error!(:some_error) + end + end + + it 'follows the errored life_cycle (skips after)' do + get '/api_call' + expect(lifecycle).to eq errored_cycle + end + end + + context 'when the api_call has an exception' do + before do + current_cycle = lifecycle + + subject.get 'api_call' do + current_cycle << :api_call + raise StandardError + end + end + + it 'follows the errored life_cycle (skips after)' do + expect { get '/api_call' }.to raise_error(StandardError) + expect(lifecycle).to eq errored_cycle + end + end + + context 'when the api_call fails validation' do + before do + current_cycle = lifecycle + + subject.params do + requires :some_param, type: String + end + + subject.get 'api_call' do + current_cycle << :api_call + end + end + + it 'follows the failed_validation cycle (skips after_validation, api_call & after)' do + get '/api_call' + expect(lifecycle).to eq validation_error + end + end + end + + describe '.finally' do + let!(:code) { { has_executed: false } } + let(:block_to_run) do + code_to_execute = code + proc do + code_to_execute[:has_executed] = true + end + end + + context 'when the ensure block has no exceptions' do + before { subject.finally(&block_to_run) } + + context 'when no API call is made' do + it 'has not executed the ensure code' do + expect(code[:has_executed]).to be false + end + end + + context 'when no errors occurs' do + before do + subject.get '/no_exceptions' do + 'success' + end + end + + it 'executes the ensure code' do + get '/no_exceptions' + expect(last_response.body).to eq 'success' + expect(code[:has_executed]).to be true + end + + context 'with a helper' do + let(:block_to_run) do + code_to_execute = code + proc do + code_to_execute[:value] = some_helper + end + end + + before do + subject.helpers do + def some_helper + 'some_value' + end + end + + subject.get '/with_helpers' do + 'success' + end + end + + it 'has access to the helper' do + get '/with_helpers' + expect(code[:value]).to eq 'some_value' + end + end + end + + context 'when an unhandled occurs inside the API call' do + before do + subject.get '/unhandled_exception' do + raise StandardError + end + end + + it 'executes the ensure code' do + expect { get '/unhandled_exception' }.to raise_error StandardError + expect(code[:has_executed]).to be true + end + end + + context 'when a handled error occurs inside the API call' do + before do + subject.rescue_from(StandardError) { error! 'handled' } + subject.get '/handled_exception' do + raise StandardError + end + end + + it 'executes the ensure code' do + get '/handled_exception' + expect(code[:has_executed]).to be true + expect(last_response.body).to eq 'handled' + end + end + end + end + describe '.rescue_from' do it 'does not rescue errors when rescue_from is not set' do subject.get '/exception' do diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 5614eea76..6bd0336f0 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -1492,6 +1492,9 @@ def memoized have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint), filters: [], type: :after }), + have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint), + filters: [], + type: :finally }), have_attributes(name: 'endpoint_run.grape', payload: { endpoint: a_kind_of(Grape::Endpoint), env: an_instance_of(Hash) }), have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash), @@ -1518,6 +1521,9 @@ def memoized have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint), filters: [], type: :after }), + have_attributes(name: 'endpoint_run_filters.grape', payload: { endpoint: a_kind_of(Grape::Endpoint), + filters: [], + type: :finally }), have_attributes(name: 'format_response.grape', payload: { env: an_instance_of(Hash), formatter: a_kind_of(Module) }) ) From cf14e2a77c37dc1ca4dbb4a7db0c7e1dc35c196a Mon Sep 17 00:00:00 2001 From: ksss Date: Mon, 11 Mar 2019 12:05:27 +0900 Subject: [PATCH 089/290] Fix NoMethodError with none Hash params --- lib/grape/validations/params_scope.rb | 1 + spec/grape/validations/params_scope_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 05c0c8c0a..bcf6753f4 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -52,6 +52,7 @@ def meets_dependency?(params, request_params) return true unless @dependent_on return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array) + return false unless params.respond_to?(:with_indifferent_access) params = params.with_indifferent_access @dependent_on.each do |dependency| diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 12865b8bc..ec03e4cb3 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -700,6 +700,26 @@ def initialize(value) end context 'when validations are dependent on a parameter within an array param' do + before do + subject.params do + requires :foos, type: Array do + optional :foo + given :foo do + requires :bar + end + end + end + subject.get('/test') { 'ok' } + end + + it 'should pass none Hash params' do + get '/test', foos: [''] + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('ok') + end + end + + context 'when validations are dependent on a parameter within an array param within #declared(params).to_json' do before do subject.params do requires :foos, type: Array do From 72da4e6e58e5746cf25b28179d3c7d1a00404b6c Mon Sep 17 00:00:00 2001 From: ksss Date: Mon, 11 Mar 2019 13:14:45 +0900 Subject: [PATCH 090/290] Add changelog for #1868 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fa5ee59..dad5001a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ #### Fixes -* Your contribution here. +* [#1868](https://github.com/ruby-grape/grape/pull/1868): Fix NoMethodError with none hash params - [@ksss](https://github.com/ksss). ### 1.2.3 (2019/01/16) From ab184d162b761bf47498d2d3a4c0878a3373341e Mon Sep 17 00:00:00 2001 From: Alexey Naumov Date: Tue, 12 Mar 2019 22:56:04 +0300 Subject: [PATCH 091/290] [#1719] Fix missing headers before error! call (#1869) --- CHANGELOG.md | 1 + README.md | 8 +++++++- UPGRADING.md | 32 ++++++++++++++++++++++++++++++++ lib/grape/dsl/inside_route.rb | 4 +++- spec/grape/endpoint_spec.rb | 30 ++++++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30fa5ee59..d195fbc3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Your contribution here. * [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh). +* [#1869](https://github.com/ruby-grape/grape/pull/1869): Fix issue with empty headers after `error!` method call - [@anaumov](https://github.com/anaumov). #### Fixes diff --git a/README.md b/README.md index 95a55a08a..49eb136ca 100644 --- a/README.md +++ b/README.md @@ -1815,7 +1815,7 @@ You can set a response header with `header` inside an API. header 'X-Robots-Tag', 'noindex' ``` -When raising `error!`, pass additional headers as arguments. +When raising `error!`, pass additional headers as arguments. Additional headers will be merged with headers set before `error!` call. ```ruby error! 'Unauthorized', 401, 'X-Error-Detail' => 'Invalid token.' @@ -2155,6 +2155,12 @@ instead of a message. error!({ error: 'unexpected error', detail: 'missing widget' }, 500) ``` +You can set additional headers for the response. They will be merged with headers set before `error!` call. + +```ruby +error!('Something went wrong', 500, 'X-Error-Detail' => 'Invalid token.') +``` + You can present documented errors with a Grape entity using the the [grape-entity](https://github.com/ruby-grape/grape-entity) gem. ```ruby diff --git a/UPGRADING.md b/UPGRADING.md index 65a829ec4..a220a468e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,38 @@ Upgrading Grape =============== +### Upgrading to >= 1.2.4 + +#### Headers in `error!` call + +Headers in `error!` will be merged with `headers` hash. If any header need to be cleared on `error!` call, make sure to move it to the `after` block. + +```ruby +class SampleApi < Grape::API + before do + header 'X-Before-Header', 'before_call' + end + + get 'ping' do + header 'X-App-Header', 'on_call' + error! :pong, 400, 'X-Error-Details' => 'Invalid token' + end +end +``` +**Former behaviour** +```ruby + response.headers['X-Before-Header'] # => nil + response.headers['X-App-Header'] # => nil + response.headers['X-Error-Details'] # => Invalid token +``` + +**Current behaviour** +```ruby + response.headers['X-Before-Header'] # => 'before_call' + response.headers['X-App-Header'] # => 'on_call' + response.headers['X-Error-Details'] # => Invalid token +``` + ### Upgrading to >= 1.2.1 #### Obtaining the name of a mounted class diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index b4672bb97..3d9248324 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -134,8 +134,10 @@ def version # # @param message [String] The message to display. # @param status [Integer] the HTTP Status Code. Defaults to default_error_status, 500 if not set. - def error!(message, status = nil, headers = nil) + # @param additional_headers [Hash] Addtional headers for the response. + def error!(message, status = nil, additional_headers = nil) self.status(status || namespace_inheritable(:default_error_status)) + headers = additional_headers.present? ? header.merge(additional_headers) : header throw :error, message: message, status: self.status, headers: headers end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 6bd0336f0..50801f947 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -1089,6 +1089,36 @@ def app expect(last_response.headers['X-Custom']).to eq('value') end + it 'merges additional headers with headers set before call' do + subject.before do + header 'X-Before-Test', 'before-sample' + end + + subject.get '/hey' do + header 'X-Test', 'test-sample' + error!({ 'dude' => 'rad' }, 403, 'X-Error' => 'error') + end + + get '/hey.json' + expect(last_response.headers['X-Before-Test']).to eq('before-sample') + expect(last_response.headers['X-Test']).to eq('test-sample') + expect(last_response.headers['X-Error']).to eq('error') + end + + it 'does not merges additional headers with headers set after call' do + subject.after do + header 'X-After-Test', 'after-sample' + end + + subject.get '/hey' do + error!({ 'dude' => 'rad' }, 403, 'X-Error' => 'error') + end + + get '/hey.json' + expect(last_response.headers['X-Error']).to eq('error') + expect(last_response.headers['X-After-Test']).to be_nil + end + it 'sets the status code for the endpoint' do memoized_endpoint = nil From 5e51364711b8725194bbe4a1e6b9878b4086cb4d Mon Sep 17 00:00:00 2001 From: Harwood <1222752+Harwood@users.noreply.github.com> Date: Thu, 14 Mar 2019 16:36:36 -0500 Subject: [PATCH 092/290] Add missing endpoint to Remounting example. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49eb136ca..853447438 100644 --- a/README.md +++ b/README.md @@ -396,7 +396,7 @@ class Comment::API < Grape::API end ``` -Assuming that the post and comment endpoints are mounted in `/posts` and `/comments`, you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes`. +Assuming that the post and comment endpoints are mounted in `/posts` and `/comments`, you should now be able to do `get /posts/votes`, `post /posts/votes`, `get /comments/votes` and `post /comments/votes`. ### Mount Configuration From 53637b7827bafdc68fc6dc739ae2facf5a1b8ca4 Mon Sep 17 00:00:00 2001 From: Franklin Yu Date: Fri, 15 Mar 2019 09:16:36 -0400 Subject: [PATCH 093/290] Remove Git.legal The service is discontinued. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 49eb136ca..3303eed3e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape) [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master) [![Inline docs](https://inch-ci.org/github/ruby-grape/grape.svg)](https://inch-ci.org/github/ruby-grape/grape) -[![git.legal](https://git.legal/projects/1364/badge.svg "Number of libraries approved")](https://git.legal/projects/1364) [![Join the chat at https://gitter.im/ruby-grape/grape](https://badges.gitter.im/ruby-grape/grape.svg)](https://gitter.im/ruby-grape/grape?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Table of Contents From 6aa32824be74f65214e81d8c0d4cb0c406887c1c Mon Sep 17 00:00:00 2001 From: Dan de Havilland Date: Tue, 16 Apr 2019 14:21:46 +0200 Subject: [PATCH 094/290] Fix const errors being hidden by bug in const_missing (#1876) --- CHANGELOG.md | 1 + lib/grape/api.rb | 2 -- spec/grape/api_spec.rb | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caba6ea58..d7e2f88ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Fixes * [#1868](https://github.com/ruby-grape/grape/pull/1868): Fix NoMethodError with none hash params - [@ksss](https://github.com/ksss). +* [#1876](https://github.com/ruby-grape/grape/pull/1876): Fix const errors being hidden by bug in `const_missing` - [@dandehavilland](https://github.com/dandehavilland). ### 1.2.3 (2019/01/16) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 918c07131..e58a7cab5 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -64,8 +64,6 @@ def api.inherited(child_api) def const_missing(*args) if base_instance.const_defined?(*args) base_instance.const_get(*args) - elsif parent && parent.const_defined?(*args) - parent.const_get(*args) else super end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 310c43dd6..889dd675e 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -3869,4 +3869,21 @@ def before expect(grape_api.eql?(MyAPI)) end end + + describe 'const_missing' do + subject(:grape_api) { Class.new(Grape::API) } + let(:mounted) do + Class.new(Grape::API) do + get '/missing' do + SomeRandomConstant + end + end + end + + before { subject.mount mounted => '/const' } + + it 'raises an error' do + expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/) + end + end end From e26ae618b86920b19b1a98945ba7d6e953a9b989 Mon Sep 17 00:00:00 2001 From: Wentao Liu Date: Fri, 19 Apr 2019 04:12:24 +1000 Subject: [PATCH 095/290] Update mounting instructions for Rails 5&6 in README (#1879) --- README.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 04b776952..cfed1fd2f 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ - [ActiveRecord without Rails](#activerecord-without-rails) - [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks) - [Rails](#rails) + - [Rails < 5.2](#rails--52) + - [Rails 6.0](#rails-60) - [Modules](#modules) - [Remounting](#remounting) - [Mount Configuration](#mount-configuration) @@ -321,6 +323,14 @@ run Rack::Cascade.new [API, Web] Place API files into `app/api`. Rails expects a subdirectory that matches the name of the Ruby module and a file name that matches the name of the class. In our example, the file name location and directory for `Twitter::API` should be `app/api/twitter/api.rb`. +Modify `config/routes`: + +```ruby +mount Twitter::API => '/' +``` + +#### Rails < 5.2 + Modify `application.rb`: ```ruby @@ -328,14 +338,18 @@ config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')] ``` -Modify `config/routes`: +See [below](#reloading-api-changes-in-development) for additional code that enables reloading of API changes in development. + +#### Rails 6.0 + +For Rails versions greater than 6.0.0.beta2, `Zeitwerk` autoloader is the default for CRuby. By default `Zeitwerk` inflects `api` as `Api` instead of `API`. To make our example work, you need to uncomment the lines at the bottom of `config/initializers/inflections.rb`, and add `API` as an acronym: ```ruby -mount Twitter::API => '/' +ActiveSupport::Inflector.inflections(:en) do |inflect| + inflect.acronym 'API' +end ``` -See [below](#reloading-api-changes-in-development) for additional code that enables reloading of API changes in development. - ### Modules You can mount multiple API implementations inside another one. These don't have to be From 62e1f3ac19e61e26784a3fba5611d7ee693bdce3 Mon Sep 17 00:00:00 2001 From: tokachev Date: Sun, 5 May 2019 21:30:31 +0300 Subject: [PATCH 096/290] Change misleading naming of alias --- README.md | 10 ++++----- lib/grape/dsl/inside_route.rb | 10 ++++----- lib/grape/endpoint.rb | 12 +++++------ lib/grape/validations/params_scope.rb | 4 ++-- lib/grape/validations/validators/as.rb | 4 ++-- spec/grape/endpoint_spec.rb | 6 +++--- spec/grape/validations/params_scope_spec.rb | 24 ++++++++++----------- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index cfed1fd2f..2d2c305a6 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ - [Validation of Nested Parameters](#validation-of-nested-parameters) - [Dependent Parameters](#dependent-parameters) - [Group Options](#group-options) - - [Alias](#alias) + - [Renaming](#renaming) - [Built-in Validators](#built-in-validators) - [allow_blank](#allow_blank) - [values](#values) @@ -1215,7 +1215,7 @@ params do end ``` -You can set alias for parameter: +You can rename parameters: ```ruby params do @@ -1226,7 +1226,7 @@ params do end ``` -Note: param in `given` should be the aliased one. In the example, it should be `type`, not `category`. +Note: param in `given` should be the renamed one. In the example, it should be `type`, not `category`. ### Group Options @@ -1255,9 +1255,9 @@ params do end ``` -### Alias +### Renaming -You can set an alias for parameters using `as`, which can be useful when refactoring existing APIs: +You can rename parameters using `as`, which can be useful when refactoring existing APIs: ```ruby resource :users do diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 3d9248324..a5faf72bc 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -61,13 +61,13 @@ def declared_hash(passed_params, options, declared_params) else # If it is not a Hash then it does not have children. # Find its value or set it to nil. - has_alias = route_setting(:aliased_params) && route_setting(:aliased_params).find { |current| current[declared_param] } - param_alias = has_alias[declared_param] if has_alias + has_renaming = route_setting(:renamed_params) && route_setting(:renamed_params).find { |current| current[declared_param] } + param_renaming = has_renaming[declared_param] if has_renaming - next unless options[:include_missing] || passed_params.key?(declared_param) || (param_alias && passed_params.key?(param_alias)) + next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming)) - if param_alias - memo[optioned_param_key(param_alias, options)] = passed_params[param_alias] + if param_renaming + memo[optioned_param_key(param_renaming, options)] = passed_params[param_renaming] else memo[optioned_param_key(declared_param, options)] = passed_params[declared_param] end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index b1ae819d4..574cb8d8f 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -258,7 +258,7 @@ def run else run_filters before_validations, :before_validation run_validators validations, request - remove_aliased_params + remove_renamed_params run_filters after_validations, :after_validation response_object = @block ? @block.call(self) : nil end @@ -324,14 +324,14 @@ def build_helpers Module.new { helpers.each { |mod_to_include| include mod_to_include } } end - def remove_aliased_params - return unless route_setting(:aliased_params) - route_setting(:aliased_params).flat_map(&:keys).each do |aliased_param| - @params.delete(aliased_param) + def remove_renamed_params + return unless route_setting(:renamed_params) + route_setting(:renamed_params).flat_map(&:keys).each do |renamed_param| + @params.delete(renamed_param) end end - private :build_stack, :build_helpers, :remove_aliased_params + private :build_stack, :build_helpers, :remove_renamed_params def helpers lazy_initialize! && @helpers diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index bcf6753f4..049eb2aed 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -120,8 +120,8 @@ def push_declared_params(attrs, **opts) @parent.push_declared_params(attrs, opts) else if opts && opts[:as] - @api.route_setting(:aliased_params, @api.route_setting(:aliased_params) || []) - @api.route_setting(:aliased_params) << { attrs.first => opts[:as] } + @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || []) + @api.route_setting(:renamed_params) << { attrs.first => opts[:as] } attrs = [opts[:as]] end diff --git a/lib/grape/validations/validators/as.rb b/lib/grape/validations/validators/as.rb index 7547c8edd..ffbbd6567 100644 --- a/lib/grape/validations/validators/as.rb +++ b/lib/grape/validations/validators/as.rb @@ -2,12 +2,12 @@ module Grape module Validations class AsValidator < Base def initialize(attrs, options, required, scope, opts = {}) - @alias = options + @renamed_options = options super end def validate_param!(attr_name, params) - params[@alias] = params[attr_name] + params[@renamed_options] = params[attr_name] end end end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 50801f947..4bf327f3f 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -472,12 +472,12 @@ def app expect(last_response.status).to eq(200) end - it 'does not include aliased missing attributes if that option is passed' do + it 'does not include renamed missing attributes if that option is passed' do subject.params do - optional :aliased_original, as: :aliased + optional :renamed_original, as: :renamed end subject.get '/declared' do - error! 'expected nil', 400 if declared(params, include_missing: false).key?(:aliased) + error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed) '' end diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index ec03e4cb3..52ff97a3c 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -121,14 +121,14 @@ def initialize(value) end end - context 'param alias' do + context 'param renaming' do it do subject.params do requires :foo, as: :bar optional :super, as: :hiper end - subject.get('/alias') { "#{declared(params)['bar']}-#{declared(params)['hiper']}" } - get '/alias', foo: 'any', super: 'any2' + subject.get('/renaming') { "#{declared(params)['bar']}-#{declared(params)['hiper']}" } + get '/renaming', foo: 'any', super: 'any2' expect(last_response.status).to eq(200) expect(last_response.body).to eq('any-any2') @@ -138,8 +138,8 @@ def initialize(value) subject.params do requires :foo, as: :bar, type: String, coerce_with: ->(c) { c.strip } end - subject.get('/alias-coerced') { "#{params['bar']}-#{params['foo']}" } - get '/alias-coerced', foo: ' there we go ' + subject.get('/renaming-coerced') { "#{params['bar']}-#{params['foo']}" } + get '/renaming-coerced', foo: ' there we go ' expect(last_response.status).to eq(200) expect(last_response.body).to eq('there we go-') @@ -149,8 +149,8 @@ def initialize(value) subject.params do requires :foo, as: :bar, allow_blank: false end - subject.get('/alias-not-blank') {} - get '/alias-not-blank', foo: '' + subject.get('/renaming-not-blank') {} + get '/renaming-not-blank', foo: '' expect(last_response.status).to eq(400) expect(last_response.body).to eq('foo is empty') @@ -160,8 +160,8 @@ def initialize(value) subject.params do requires :foo, as: :bar, allow_blank: false end - subject.get('/alias-not-blank-with-value') {} - get '/alias-not-blank-with-value', foo: 'any' + subject.get('/renaming-not-blank-with-value') {} + get '/renaming-not-blank-with-value', foo: 'any' expect(last_response.status).to eq(200) end @@ -532,7 +532,7 @@ def initialize(value) expect(last_response.body).to eq({ a: 'a', b: 'b', c: 'c', d: 'd' }.to_json) end - it 'allows aliasing of dependent parameters' do + it 'allows renaming of dependent parameters' do subject.params do optional :a given :a do @@ -550,7 +550,7 @@ def initialize(value) expect(body.keys).to_not include('b') end - it 'allows aliasing of dependent on parameter' do + it 'allows renaming of dependent on parameter' do subject.params do optional :a, as: :b given b: ->(val) { val == 'x' } do @@ -567,7 +567,7 @@ def initialize(value) expect(last_response.status).to eq 200 end - it 'raises an error if the dependent parameter is not the aliased one' do + it 'raises an error if the dependent parameter is not the renamed one' do expect do subject.params do optional :a, as: :b From f02129282bef07281a99fcd4c7fd6b293dd54a3d Mon Sep 17 00:00:00 2001 From: Carl Leiby Date: Fri, 17 May 2019 10:25:38 -0400 Subject: [PATCH 097/290] Documentation to explain request header casing (#1887) Closes #1882 --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 2d2c305a6..b12ed6a70 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,9 @@ - [Overriding Attribute Names](#overriding-attribute-names) - [With Default](#with-default) - [Headers](#headers) + - [Request](#request) + - [Header Case Handling](#header-case-handling) + - [Response](#response) - [Routes](#routes) - [Helpers](#helpers) - [Path Helpers](#path-helpers) @@ -1808,6 +1811,7 @@ end ## Headers +### Request Request headers are available through the `headers` helper or from `env` in their original form. ```ruby @@ -1822,6 +1826,23 @@ get do end ``` +#### Header Case Handling + +The above example may have been requested as follows: + +``` shell +curl -H "secret_PassWord: swordfish" ... +``` + +The header name will have been normalized for you. + +- In the `header` helper names will be coerced into a capitalized kebab case. +- In the `env` collection they appear in all uppercase, in snake case, and prefixed with 'HTTP_'. + +The header name will have been normalized per HTTP standards defined in [RFC2616 Section 4.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2) regardless of what is being sent by a client. + +### Response + You can set a response header with `header` inside an API. ```ruby From 0c5ce7ee48f85b737b5d7645e7aacd165e7244d7 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 5 Jun 2019 19:02:15 +0100 Subject: [PATCH 098/290] Adds configurations in various scopes --- lib/grape.rb | 2 + lib/grape/api.rb | 35 ++++-- lib/grape/api/instance.rb | 4 + lib/grape/dsl/inside_route.rb | 4 + lib/grape/util/endpoint_configuration.rb | 6 + lib/grape/util/lazy_value.rb | 90 ++++++++++++++ lib/grape/validations/params_scope.rb | 4 + spec/grape/api_remount_spec.rb | 144 +++++++++++++++++++++-- 8 files changed, 270 insertions(+), 19 deletions(-) create mode 100644 lib/grape/util/endpoint_configuration.rb create mode 100644 lib/grape/util/lazy_value.rb diff --git a/lib/grape.rb b/lib/grape.rb index 9cfcfceca..b48cdf42e 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -195,6 +195,8 @@ module ServeFile require 'grape/config' require 'grape/util/content_types' +require 'grape/util/lazy_value' +require 'grape/util/endpoint_configuration' require 'grape/validations/validators/base' require 'grape/validations/attributes_iterator' diff --git a/lib/grape/api.rb b/lib/grape/api.rb index e58a7cab5..259b8f523 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = (Class.new.methods + %i[call call!]).freeze + NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration]).freeze class << self attr_accessor :base_instance, :instances @@ -75,7 +75,7 @@ def const_missing(*args) # too much, you may actually want to provide a new API rather than remount it. def mount_instance(opts = {}) instance = Class.new(@base_parent) - instance.configuration = opts[:configuration] || {} + instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {}) instance.base = self replay_setup_on(instance) instance @@ -84,8 +84,8 @@ def mount_instance(opts = {}) # Replays the set up to produce an API as defined in this class, can be called # on classes that inherit from Grape::API def replay_setup_on(instance) - @setup.each do |setup_stage| - instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) + @setup.each do |setup_step| + replay_step_on(instance, setup_step) end end @@ -110,14 +110,35 @@ def method_missing(method, *args, &block) # Adds a new stage to the set up require to get a Grape::API up and running def add_setup(method, *args, &block) - setup_stage = { method: method, args: args, block: block } - @setup << setup_stage + setup_step = { method: method, args: args, block: block } + @setup << setup_step last_response = nil @instances.each do |instance| - last_response = instance.send(setup_stage[:method], *setup_stage[:args], &setup_stage[:block]) + last_response = replay_step_on(instance, setup_step) end last_response end + + def replay_step_on(instance, setup_step) + return if skip_immediate_run?(instance, setup_step[:args]) + instance.send(setup_step[:method], *evaluate_arguments(setup_step[:args], instance.configuration), &setup_step[:block]) + end + + # Skips steps that contain arguments to be lazily executed (on re-mount time) + def skip_immediate_run?(instance, args) + instance.base_instance? && + args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? } + end + + def evaluate_arguments(args, configuration) + args.map do |argument| + if argument.respond_to?(:lazy?) && argument.lazy? + configuration.fetch(argument.access_keys).evaluate + else + argument + end + end + end end end end diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 2fdb91fa1..b42bc1f87 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -21,6 +21,10 @@ def to_s (base && base.to_s) || super end + def base_instance? + self == base.base_instance + end + # A class-level lock to ensure the API is not compiled by multiple # threads simultaneously within the same process. LOCK = Mutex.new diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index a5faf72bc..2953d590d 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -129,6 +129,10 @@ def version env[Grape::Env::API_VERSION] end + def configuration + options[:for].configuration.evaluate + end + # End the request and display an error to the # end user with the specified message. # diff --git a/lib/grape/util/endpoint_configuration.rb b/lib/grape/util/endpoint_configuration.rb new file mode 100644 index 000000000..27296a578 --- /dev/null +++ b/lib/grape/util/endpoint_configuration.rb @@ -0,0 +1,6 @@ +module Grape + module Util + class EndpointConfiguration < LazyValueHash + end + end +end diff --git a/lib/grape/util/lazy_value.rb b/lib/grape/util/lazy_value.rb new file mode 100644 index 000000000..91add6508 --- /dev/null +++ b/lib/grape/util/lazy_value.rb @@ -0,0 +1,90 @@ +module Grape + module Util + class LazyValue + attr_reader :access_keys + def initialize(value, access_keys = []) + @value = value + @access_keys = access_keys + end + + def evaluate + @value + end + + def lazy? + true + end + + def reached_by(parent_access_keys, access_key) + @access_keys = parent_access_keys + [access_key] + self + end + + def to_s + evaluate.to_s + end + end + + class LazyValueEnumerable < LazyValue + def [](key) + if @value_hash[key].nil? + LazyValue.new(nil).reached_by(access_keys, key) + else + @value_hash[key].reached_by(access_keys, key) + end + end + + def fetch(access_keys) + fetched_keys = access_keys.dup + value = self[fetched_keys.shift] + fetched_keys.any? ? value.fetch(fetched_keys) : value + end + + def []=(key, value) + @value_hash[key] = if value.is_a?(Hash) + LazyValueHash.new(value) + elsif value.is_a?(Array) + LazyValueArray.new(value) + else + LazyValue.new(value) + end + end + end + + class LazyValueArray < LazyValueEnumerable + def initialize(array) + super + @value_hash = [] + array.each_with_index do |value, index| + self[index] = value + end + end + + def evaluate + evaluated = [] + @value_hash.each_with_index do |value, index| + evaluated[index] = value.evaluate + end + evaluated + end + end + + class LazyValueHash < LazyValueEnumerable + def initialize(hash) + super + @value_hash = {}.with_indifferent_access + hash.each do |key, value| + self[key] = value + end + end + + def evaluate + evaluated = {}.with_indifferent_access + @value_hash.each do |key, value| + evaluated[key] = value.evaluate + end + evaluated + end + end + end +end diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 049eb2aed..728901139 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -36,6 +36,10 @@ def initialize(opts, &block) configure_declared_params end + def configuration + @api.configuration.evaluate + end + # @return [Boolean] whether or not this entire scope needs to be # validated def should_validate?(parameters) diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index 76d2625fd..c8393bd87 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -63,22 +63,142 @@ def app end end - context 'with a dynamically configured route' do - before do - a_remounted_api.namespace 'api' do - get "/#{configuration[:path]}" do - '10 votes' + describe 'with dynamic configuration' do + context 'when the configuration is part of the arguments of a method' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + get configuration[:endpoint_name] do + 'success' + end + end + end + + it 'mounts the endpoint in the location it is configured' do + root_api.mount a_remounted_api, with: { endpoint_name: 'some_location' } + get '/some_location' + expect(last_response.body).to eq 'success' + + get '/different_location' + expect(last_response.status).to eq 404 + + root_api.mount a_remounted_api, with: { endpoint_name: 'new_location' } + get '/new_location' + expect(last_response.body).to eq 'success' + end + end + + context 'on the ParamScope' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + params do + requires configuration[:required_param], type: configuration[:required_type] + end + + get 'location' do + 'success' + end + end + end + + it 'mounts the endpoint in the location it is configured' do + root_api.mount({ a_remounted_api => 'string' }, with: { required_param: 'param_key', required_type: String }) + root_api.mount({ a_remounted_api => 'integer' }, with: { required_param: 'param_integer', required_type: Integer }) + + get '/string/location', param_key: 'a' + expect(last_response.body).to eq 'success' + + get '/string/location', param_integer: 1 + expect(last_response.status).to eq 400 + + get '/integer/location', param_integer: 1 + expect(last_response.body).to eq 'success' + + get '/integer/location', param_integer: 'a' + expect(last_response.status).to eq 400 + end + + context 'on dynamic checks' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + params do + optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] } + end + + get 'location' do + 'success' + end + end + end + + it 'can read the configuration on lambdas' do + root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' } + get '/location', restricted_values: 'always' + expect(last_response.body).to eq 'success' + get '/location', restricted_values: 'sometimes' + expect(last_response.body).to eq 'success' + get '/location', restricted_values: 'never' + expect(last_response.status).to eq 400 end end - root_api.mount a_remounted_api, with: { path: 'votes' } - root_api.mount a_remounted_api, with: { path: 'scores' } end - it 'will use the dynamic configuration on all routes' do - get 'api/votes' - expect(last_response.body).to eql '10 votes' - get 'api/scores' - expect(last_response.body).to eql '10 votes' + context 'when the configuration is read within a namespace' do + before do + a_remounted_api.namespace 'api' do + get "/#{configuration[:path]}" do + '10 votes' + end + end + root_api.mount a_remounted_api, with: { path: 'votes' } + root_api.mount a_remounted_api, with: { path: 'scores' } + end + + it 'will use the dynamic configuration on all routes' do + get 'api/votes' + expect(last_response.body).to eql '10 votes' + get 'api/scores' + expect(last_response.body).to eql '10 votes' + end + end + + context 'when the configuration is read in a helper' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + helpers do + def printed_response + configuration[:some_value] + end + end + + get 'location' do + printed_response + end + end + end + + it 'will use the dynamic configuration on all routes' do + root_api.mount(a_remounted_api, with: { some_value: 'response value' }) + + get '/location' + expect(last_response.body).to eq 'response value' + end + end + + context 'when the configuration is read within the response block' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + get 'location' do + configuration[:some_value] + end + end + end + + it 'will use the dynamic configuration on all routes' do + root_api.mount(a_remounted_api, with: { some_value: 'response value' }) + + get '/location' + expect(last_response.body).to eq 'response value' + end end end end From 69f23372755ef8709426338c2cf7d142c9bebb76 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 6 Jun 2019 14:27:47 +0100 Subject: [PATCH 099/290] Adds configuration to describe param --- lib/grape/api.rb | 8 ++++-- lib/grape/dsl/desc.rb | 7 ++++-- spec/grape/api_remount_spec.rb | 46 ++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 259b8f523..56571d8fc 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -121,7 +121,7 @@ def add_setup(method, *args, &block) def replay_step_on(instance, setup_step) return if skip_immediate_run?(instance, setup_step[:args]) - instance.send(setup_step[:method], *evaluate_arguments(setup_step[:args], instance.configuration), &setup_step[:block]) + instance.send(setup_step[:method], *evaluate_arguments(instance.configuration, *setup_step[:args]), &setup_step[:block]) end # Skips steps that contain arguments to be lazily executed (on re-mount time) @@ -130,10 +130,14 @@ def skip_immediate_run?(instance, args) args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? } end - def evaluate_arguments(args, configuration) + def evaluate_arguments(configuration, *args) args.map do |argument| if argument.respond_to?(:lazy?) && argument.lazy? configuration.fetch(argument.access_keys).evaluate + elsif argument.is_a?(Hash) + argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h + elsif argument.is_a?(Array) + evaluate_arguments(configuration, *argument) else argument end diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index d13a451ee..4690ae7bf 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -49,7 +49,7 @@ module Desc # def desc(description, options = {}, &config_block) if block_given? - config_class = desc_container + config_class = desc_container(configuration.evaluate) config_class.configure do description description @@ -84,7 +84,7 @@ def unset_description_field(field) end # Returns an object which configures itself via an instance-context DSL. - def desc_container + def desc_container(endpoint_configuration) Module.new do include Grape::Util::StrictHashConfiguration.module( :summary, @@ -105,6 +105,9 @@ def desc_container :security, :tags ) + config_context.define_singleton_method(:configuration) do + endpoint_configuration + end def config_context.success(*args) entity(*args) diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index c8393bd87..7d103b331 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -85,6 +85,52 @@ def app get '/new_location' expect(last_response.body).to eq 'success' end + + context 'when the configuration is the value in a key-arg pair' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + version 'v1', using: :param, parameter: configuration[:version_param] + get 'endpoint' do + 'version 1' + end + + version 'v2', using: :param, parameter: configuration[:version_param] + get 'endpoint' do + 'version 2' + end + end + end + + it 'takes the param from the configuration' do + root_api.mount a_remounted_api, with: { version_param: 'param_name' } + + get '/endpoint?param_name=v1' + expect(last_response.body).to eq 'version 1' + + get '/endpoint?param_name=v2' + expect(last_response.body).to eq 'version 2' + + get '/endpoint?wrong_param_name=v2' + expect(last_response.body).to eq 'version 1' + end + end + end + + context 'on the DescSCope' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + desc 'The description of this' do + tags ['not_configurable_tag', configuration[:a_configurable_tag]] + end + get 'location' do + 'success' + end + end + end + + it 'mounts the endpoint with the appropiate tags' do + root_api.mount({ a_remounted_api => 'integer' }, with: { a_configurable_tag: 'a configured tag' }) + end end context 'on the ParamScope' do From e9780494932025686e2c758b9a4fb0e0767507be Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 6 Jun 2019 14:35:59 +0100 Subject: [PATCH 100/290] Rename configuration to api_configuration --- lib/grape/api.rb | 4 +-- lib/grape/api/instance.rb | 15 ++++++++-- lib/grape/dsl/desc.rb | 5 ++-- lib/grape/dsl/inside_route.rb | 4 +-- lib/grape/dsl/routing.rb | 2 +- lib/grape/validations/params_scope.rb | 4 +-- spec/grape/api_remount_spec.rb | 40 +++++++++++++-------------- 7 files changed, 43 insertions(+), 31 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 56571d8fc..4dd762012 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -75,7 +75,7 @@ def const_missing(*args) # too much, you may actually want to provide a new API rather than remount it. def mount_instance(opts = {}) instance = Class.new(@base_parent) - instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {}) + instance.api_configuration = Grape::Util::EndpointConfiguration.new(opts[:api_configuration] || {}) instance.base = self replay_setup_on(instance) instance @@ -121,7 +121,7 @@ def add_setup(method, *args, &block) def replay_step_on(instance, setup_step) return if skip_immediate_run?(instance, setup_step[:args]) - instance.send(setup_step[:method], *evaluate_arguments(instance.configuration, *setup_step[:args]), &setup_step[:block]) + instance.send(setup_step[:method], *evaluate_arguments(instance.api_configuration, *setup_step[:args]), &setup_step[:block]) end # Skips steps that contain arguments to be lazily executed (on re-mount time) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index b42bc1f87..61467e825 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -10,7 +10,18 @@ class Instance class << self attr_reader :instance attr_reader :base - attr_accessor :configuration + attr_accessor :api_configuration + + # DEPRECATED + def configuration + warn 'Accessing the configuration with configuration is deprecated, use api_configuration instead' + api_configuration + end + + def configuration=(value) + warn 'Accessing the configuration with configuration= is deprecated, use api_configuration= instead' + self.api_configuration = value + end def base=(grape_api) @base = grape_api @@ -219,7 +230,7 @@ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) end - # Allows definition of endpoints that ignore the versioning configuration + # Allows definition of endpoints that ignore the versioning api_configuration # used by the rest of your API. def without_versioning(&_block) old_version = self.class.namespace_inheritable(:version) diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index 4690ae7bf..cd9738d11 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -49,7 +49,8 @@ module Desc # def desc(description, options = {}, &config_block) if block_given? - config_class = desc_container(configuration.evaluate) + configuration = defined?(api_configuration) ? api_configuration.evaluate : {} + config_class = desc_container(configuration) config_class.configure do description description @@ -105,7 +106,7 @@ def desc_container(endpoint_configuration) :security, :tags ) - config_context.define_singleton_method(:configuration) do + config_context.define_singleton_method(:api_configuration) do endpoint_configuration end diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 2953d590d..4ad6e10a3 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -129,8 +129,8 @@ def version env[Grape::Env::API_VERSION] end - def configuration - options[:for].configuration.evaluate + def api_configuration + options[:for].api_configuration.evaluate end # End the request and display an error to the diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index bf5f4c65a..eb3c7a237 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -81,7 +81,7 @@ def mount(mounts, opts = {}) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:mount_instance) - mount(app.mount_instance(configuration: opts[:with] || {}) => path) + mount(app.mount_instance(api_configuration: opts[:with] || {}) => path) next end in_setting = inheritable_setting diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 728901139..fedc28005 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -36,8 +36,8 @@ def initialize(opts, &block) configure_declared_params end - def configuration - @api.configuration.evaluate + def api_configuration + @api.api_configuration.evaluate end # @return [Boolean] whether or not this entire scope needs to be diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index 7d103b331..7429c1105 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -63,11 +63,11 @@ def app end end - describe 'with dynamic configuration' do - context 'when the configuration is part of the arguments of a method' do + describe 'with dynamic api_configuration' do + context 'when the api_configuration is part of the arguments of a method' do subject(:a_remounted_api) do Class.new(Grape::API) do - get configuration[:endpoint_name] do + get api_configuration[:endpoint_name] do 'success' end end @@ -86,22 +86,22 @@ def app expect(last_response.body).to eq 'success' end - context 'when the configuration is the value in a key-arg pair' do + context 'when the api_configuration is the value in a key-arg pair' do subject(:a_remounted_api) do Class.new(Grape::API) do - version 'v1', using: :param, parameter: configuration[:version_param] + version 'v1', using: :param, parameter: api_configuration[:version_param] get 'endpoint' do 'version 1' end - version 'v2', using: :param, parameter: configuration[:version_param] + version 'v2', using: :param, parameter: api_configuration[:version_param] get 'endpoint' do 'version 2' end end end - it 'takes the param from the configuration' do + it 'takes the param from the api_configuration' do root_api.mount a_remounted_api, with: { version_param: 'param_name' } get '/endpoint?param_name=v1' @@ -120,7 +120,7 @@ def app subject(:a_remounted_api) do Class.new(Grape::API) do desc 'The description of this' do - tags ['not_configurable_tag', configuration[:a_configurable_tag]] + tags ['not_configurable_tag', api_configuration[:a_configurable_tag]] end get 'location' do 'success' @@ -137,7 +137,7 @@ def app subject(:a_remounted_api) do Class.new(Grape::API) do params do - requires configuration[:required_param], type: configuration[:required_type] + requires api_configuration[:required_param], type: api_configuration[:required_type] end get 'location' do @@ -167,7 +167,7 @@ def app subject(:a_remounted_api) do Class.new(Grape::API) do params do - optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] } + optional :restricted_values, values: -> { [api_configuration[:allowed_value], 'always'] } end get 'location' do @@ -176,7 +176,7 @@ def app end end - it 'can read the configuration on lambdas' do + it 'can read the api_configuration on lambdas' do root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' } get '/location', restricted_values: 'always' expect(last_response.body).to eq 'success' @@ -188,10 +188,10 @@ def app end end - context 'when the configuration is read within a namespace' do + context 'when the api_configuration is read within a namespace' do before do a_remounted_api.namespace 'api' do - get "/#{configuration[:path]}" do + get "/#{api_configuration[:path]}" do '10 votes' end end @@ -199,7 +199,7 @@ def app root_api.mount a_remounted_api, with: { path: 'scores' } end - it 'will use the dynamic configuration on all routes' do + it 'will use the dynamic api_configuration on all routes' do get 'api/votes' expect(last_response.body).to eql '10 votes' get 'api/scores' @@ -207,12 +207,12 @@ def app end end - context 'when the configuration is read in a helper' do + context 'when the api_configuration is read in a helper' do subject(:a_remounted_api) do Class.new(Grape::API) do helpers do def printed_response - configuration[:some_value] + api_configuration[:some_value] end end @@ -222,7 +222,7 @@ def printed_response end end - it 'will use the dynamic configuration on all routes' do + it 'will use the dynamic api_configuration on all routes' do root_api.mount(a_remounted_api, with: { some_value: 'response value' }) get '/location' @@ -230,16 +230,16 @@ def printed_response end end - context 'when the configuration is read within the response block' do + context 'when the api_configuration is read within the response block' do subject(:a_remounted_api) do Class.new(Grape::API) do get 'location' do - configuration[:some_value] + api_configuration[:some_value] end end end - it 'will use the dynamic configuration on all routes' do + it 'will use the dynamic api_configuration on all routes' do root_api.mount(a_remounted_api, with: { some_value: 'response value' }) get '/location' From 4e00b074f4cae39d3e9978bc35cc0ecb9b62b6fe Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 6 Jun 2019 15:41:45 +0100 Subject: [PATCH 101/290] More dynamic configurations --- lib/grape/api.rb | 8 +++-- lib/grape/api/instance.rb | 24 ++++++++++++-- spec/grape/api_remount_spec.rb | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 4dd762012..9566092b4 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration]).freeze + NON_OVERRIDABLE = (Class.new.methods + %i[call call! api_configuration]).freeze class << self attr_accessor :base_instance, :instances @@ -127,7 +127,11 @@ def replay_step_on(instance, setup_step) # Skips steps that contain arguments to be lazily executed (on re-mount time) def skip_immediate_run?(instance, args) instance.base_instance? && - args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? } + (any_lazy?(args) || args.any? { |arg| arg.is_a?(Hash) && any_lazy?(arg.values) }) + end + + def any_lazy?(args) + args.any? { |argument| argument.respond_to?(:lazy?) && argument.lazy? } end def evaluate_arguments(configuration, *args) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 61467e825..b2c9143b4 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -11,7 +11,6 @@ class << self attr_reader :instance attr_reader :base attr_accessor :api_configuration - # DEPRECATED def configuration warn 'Accessing the configuration with configuration is deprecated, use api_configuration instead' @@ -23,6 +22,15 @@ def configuration=(value) self.api_configuration = value end + def conditional(on:, &block) + evaluate_as_instance_with_api_configuration(block) if on && block_given? + end + + def on_mounted(&block) + return if base_instance? + evaluate_as_instance_with_api_configuration(block) + end + def base=(grape_api) @base = grape_api grape_api.instances << self @@ -99,14 +107,24 @@ def prepare_routes def nest(*blocks, &block) blocks.reject!(&:nil?) if blocks.any? - instance_eval(&block) if block_given? - blocks.each { |b| instance_eval(&b) } + evaluate_as_instance_with_api_configuration(block) if block_given? + blocks.each { |b| evaluate_as_instance_with_api_configuration(b) } reset_validations! else instance_eval(&block) end end + def evaluate_as_instance_with_api_configuration(block) + value_for_api_configuration = api_configuration + if value_for_api_configuration.respond_to?(:lazy?) && value_for_api_configuration.lazy? + self.api_configuration = value_for_api_configuration.evaluate + end + response = instance_eval(&block) + self.api_configuration = value_for_api_configuration + response + end + def inherited(subclass) subclass.reset! subclass.logger = logger.clone diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index 7429c1105..d810aff9a 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -64,6 +64,63 @@ def app end describe 'with dynamic api_configuration' do + context 'when mounting an endpoint conditional on a configuration' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + get 'always' do + 'success' + end + + conditional on: api_configuration[:mount_sometimes] do + get 'sometimes' do + 'sometimes' + end + end + end + end + + it 'mounts the endpoints only when configured to do so' do + root_api.mount({ a_remounted_api => 'with_conditional' }, with: { mount_sometimes: true }) + root_api.mount({ a_remounted_api => 'without_conditional' }, with: { mount_sometimes: false }) + + get '/with_conditional/always' + expect(last_response.body).to eq 'success' + + get '/with_conditional/sometimes' + expect(last_response.body).to eq 'sometimes' + + get '/without_conditional/always' + expect(last_response.body).to eq 'success' + + get '/without_conditional/sometimes' + expect(last_response.status).to eq 404 + end + end + + context 'when executing a custom block on mount' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + get 'always' do + 'success' + end + + on_mounted do + api_configuration[:endpoints].each do |endpoint_name, endpoint_response| + get endpoint_name do + endpoint_response + end + end + end + end + end + + it 'mounts the endpoints only when configured to do so' do + root_api.mount a_remounted_api, with: { endpoints: { 'api_name' => 'api_response' } } + get 'api_name' + expect(last_response.body).to eq 'api_response' + end + end + context 'when the api_configuration is part of the arguments of a method' do subject(:a_remounted_api) do Class.new(Grape::API) do From 0cd837556e4df497c97ddf43599d0155b837f7ee Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 6 Jun 2019 15:49:55 +0100 Subject: [PATCH 102/290] Updates documentation --- README.md | 33 +++++++++++++++++++++++++++++++-- UPGRADING.md | 25 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b12ed6a70..46f93d444 100644 --- a/README.md +++ b/README.md @@ -416,12 +416,12 @@ Assuming that the post and comment endpoints are mounted in `/posts` and `/comme ### Mount Configuration -You can configure remountable endpoints to change small details according to where they are mounted. +You can configure remountable endpoints to change how they behave according to where they are mounted. ```ruby class Voting::API < Grape::API namespace 'votes' do - desc "Vote for your #{configuration[:votable]}" + desc "Vote for your #{api_configuration[:votable]}" get do # Your logic end @@ -437,6 +437,35 @@ class Comment::API < Grape::API end ``` +You can access `api_configuration` on the class (to use as dynamic attributes), inside blocks (like namespace) + +If you want logic happening conditional on an `api_configuration`, you can use the helper `conditional`. + +```ruby +class ConditionalEndpoint::API < Grape::API + conditional on: api_configuration[:some_setting] do + get 'mount_this_endpoint_conditionally' do + api_configuration[:configurable_response] + end + end +end +``` + +If you want a block of logic running every time an endpoint is mounted (within which you can access the `api_configuration` Hash) + + +```ruby +class ConditionalEndpoint::API < Grape::API + on_mounted do + YourLogger.info "This API was mounted at: #{Time.now}" + + get api_configuration[:endpoint_name] do + api_configuration[:configurable_response] + end + end +end +``` + ## Versioning There are four strategies in which clients can reach your API's endpoints: `:path`, diff --git a/UPGRADING.md b/UPGRADING.md index a220a468e..c9dc71105 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,31 @@ Upgrading Grape ### Upgrading to >= 1.2.4 +#### Configuration Block + +The configuration block has been greatly enhanced (it can now be accessed in pretty much every block), to avoid name conflicts it has been renamed to +`api_configuration` in most places, however, in those places where it was accessible previously, `configuration` will still work (but raise deprecation message) + +For example: + +**Deprecated in 1.2.4** +```ruby + class YourAPI < Grape::API + desc configuration[:configurable_description] do + # your config block here... + end + end +``` +**New** +```ruby + class YourAPI < Grape::API + desc api_configuration[:configurable_description] do + # your config block here... + end + end +``` + + #### Headers in `error!` call Headers in `error!` will be merged with `headers` hash. If any header need to be cleared on `error!` call, make sure to move it to the `after` block. From 2fb8cdc51df4b9d216cec076f592ef03452aafd1 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 6 Jun 2019 15:58:16 +0100 Subject: [PATCH 103/290] Adds to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e2f88ae..7c43a8fa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1888](https://github.com/ruby-grape/grape/pull/1888): Renames the `configuration` block and makes it widly available as `api_configuration` - [@myxoh](https://github.com/myxoh). * [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh). * [#1869](https://github.com/ruby-grape/grape/pull/1869): Fix issue with empty headers after `error!` method call - [@anaumov](https://github.com/anaumov). From 8fccca3c4a06bd4d36b1dca60591a7050a32d9f4 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Mon, 10 Jun 2019 13:28:12 +0100 Subject: [PATCH 104/290] Renames api_configuration to configuration --- CHANGELOG.md | 2 +- README.md | 16 +++++----- UPGRADING.md | 25 --------------- lib/grape/api.rb | 6 ++-- lib/grape/api/instance.rb | 32 +++++++------------ lib/grape/dsl/desc.rb | 4 +-- lib/grape/dsl/inside_route.rb | 4 +-- lib/grape/dsl/routing.rb | 2 +- lib/grape/validations/params_scope.rb | 4 +-- spec/grape/api_remount_spec.rb | 44 +++++++++++++-------------- 10 files changed, 52 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c43a8fa5..7c1656e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ #### Features * Your contribution here. -* [#1888](https://github.com/ruby-grape/grape/pull/1888): Renames the `configuration` block and makes it widly available as `api_configuration` - [@myxoh](https://github.com/myxoh). +* [#1888](https://github.com/ruby-grape/grape/pull/1888): Makes the `configuration` hash widly available - [@myxoh](https://github.com/myxoh). * [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh). * [#1869](https://github.com/ruby-grape/grape/pull/1869): Fix issue with empty headers after `error!` method call - [@anaumov](https://github.com/anaumov). diff --git a/README.md b/README.md index 46f93d444..ebede2857 100644 --- a/README.md +++ b/README.md @@ -421,7 +421,7 @@ You can configure remountable endpoints to change how they behave according to w ```ruby class Voting::API < Grape::API namespace 'votes' do - desc "Vote for your #{api_configuration[:votable]}" + desc "Vote for your #{configuration[:votable]}" get do # Your logic end @@ -437,21 +437,21 @@ class Comment::API < Grape::API end ``` -You can access `api_configuration` on the class (to use as dynamic attributes), inside blocks (like namespace) +You can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace) -If you want logic happening conditional on an `api_configuration`, you can use the helper `conditional`. +If you want logic happening conditional on an `configuration`, you can use the helper `conditional`. ```ruby class ConditionalEndpoint::API < Grape::API - conditional on: api_configuration[:some_setting] do + conditional on: configuration[:some_setting] do get 'mount_this_endpoint_conditionally' do - api_configuration[:configurable_response] + configuration[:configurable_response] end end end ``` -If you want a block of logic running every time an endpoint is mounted (within which you can access the `api_configuration` Hash) +If you want a block of logic running every time an endpoint is mounted (within which you can access the `configuration` Hash) ```ruby @@ -459,8 +459,8 @@ class ConditionalEndpoint::API < Grape::API on_mounted do YourLogger.info "This API was mounted at: #{Time.now}" - get api_configuration[:endpoint_name] do - api_configuration[:configurable_response] + get configuration[:endpoint_name] do + configuration[:configurable_response] end end end diff --git a/UPGRADING.md b/UPGRADING.md index c9dc71105..a220a468e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,31 +3,6 @@ Upgrading Grape ### Upgrading to >= 1.2.4 -#### Configuration Block - -The configuration block has been greatly enhanced (it can now be accessed in pretty much every block), to avoid name conflicts it has been renamed to -`api_configuration` in most places, however, in those places where it was accessible previously, `configuration` will still work (but raise deprecation message) - -For example: - -**Deprecated in 1.2.4** -```ruby - class YourAPI < Grape::API - desc configuration[:configurable_description] do - # your config block here... - end - end -``` -**New** -```ruby - class YourAPI < Grape::API - desc api_configuration[:configurable_description] do - # your config block here... - end - end -``` - - #### Headers in `error!` call Headers in `error!` will be merged with `headers` hash. If any header need to be cleared on `error!` call, make sure to move it to the `after` block. diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 9566092b4..d4b70ae3e 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = (Class.new.methods + %i[call call! api_configuration]).freeze + NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration]).freeze class << self attr_accessor :base_instance, :instances @@ -75,7 +75,7 @@ def const_missing(*args) # too much, you may actually want to provide a new API rather than remount it. def mount_instance(opts = {}) instance = Class.new(@base_parent) - instance.api_configuration = Grape::Util::EndpointConfiguration.new(opts[:api_configuration] || {}) + instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {}) instance.base = self replay_setup_on(instance) instance @@ -121,7 +121,7 @@ def add_setup(method, *args, &block) def replay_step_on(instance, setup_step) return if skip_immediate_run?(instance, setup_step[:args]) - instance.send(setup_step[:method], *evaluate_arguments(instance.api_configuration, *setup_step[:args]), &setup_step[:block]) + instance.send(setup_step[:method], *evaluate_arguments(instance.configuration, *setup_step[:args]), &setup_step[:block]) end # Skips steps that contain arguments to be lazily executed (on re-mount time) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index b2c9143b4..42edc63fc 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -10,25 +10,15 @@ class Instance class << self attr_reader :instance attr_reader :base - attr_accessor :api_configuration - # DEPRECATED - def configuration - warn 'Accessing the configuration with configuration is deprecated, use api_configuration instead' - api_configuration - end - - def configuration=(value) - warn 'Accessing the configuration with configuration= is deprecated, use api_configuration= instead' - self.api_configuration = value - end + attr_accessor :configuration def conditional(on:, &block) - evaluate_as_instance_with_api_configuration(block) if on && block_given? + evaluate_as_instance_with_configuration(block) if on && block_given? end def on_mounted(&block) return if base_instance? - evaluate_as_instance_with_api_configuration(block) + evaluate_as_instance_with_configuration(block) end def base=(grape_api) @@ -107,21 +97,21 @@ def prepare_routes def nest(*blocks, &block) blocks.reject!(&:nil?) if blocks.any? - evaluate_as_instance_with_api_configuration(block) if block_given? - blocks.each { |b| evaluate_as_instance_with_api_configuration(b) } + evaluate_as_instance_with_configuration(block) if block_given? + blocks.each { |b| evaluate_as_instance_with_configuration(b) } reset_validations! else instance_eval(&block) end end - def evaluate_as_instance_with_api_configuration(block) - value_for_api_configuration = api_configuration - if value_for_api_configuration.respond_to?(:lazy?) && value_for_api_configuration.lazy? - self.api_configuration = value_for_api_configuration.evaluate + def evaluate_as_instance_with_configuration(block) + value_for_configuration = configuration + if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy? + self.configuration = value_for_configuration.evaluate end response = instance_eval(&block) - self.api_configuration = value_for_api_configuration + self.configuration = value_for_configuration response end @@ -248,7 +238,7 @@ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) end - # Allows definition of endpoints that ignore the versioning api_configuration + # Allows definition of endpoints that ignore the versioning configuration # used by the rest of your API. def without_versioning(&_block) old_version = self.class.namespace_inheritable(:version) diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index cd9738d11..865da1ed7 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -49,7 +49,7 @@ module Desc # def desc(description, options = {}, &config_block) if block_given? - configuration = defined?(api_configuration) ? api_configuration.evaluate : {} + configuration = defined?(configuration) && configuration.respond_to?(:evaluate) ? configuration.evaluate : {} config_class = desc_container(configuration) config_class.configure do @@ -106,7 +106,7 @@ def desc_container(endpoint_configuration) :security, :tags ) - config_context.define_singleton_method(:api_configuration) do + config_context.define_singleton_method(:configuration) do endpoint_configuration end diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 4ad6e10a3..2953d590d 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -129,8 +129,8 @@ def version env[Grape::Env::API_VERSION] end - def api_configuration - options[:for].api_configuration.evaluate + def configuration + options[:for].configuration.evaluate end # End the request and display an error to the diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index eb3c7a237..bf5f4c65a 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -81,7 +81,7 @@ def mount(mounts, opts = {}) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:mount_instance) - mount(app.mount_instance(api_configuration: opts[:with] || {}) => path) + mount(app.mount_instance(configuration: opts[:with] || {}) => path) next end in_setting = inheritable_setting diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index fedc28005..728901139 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -36,8 +36,8 @@ def initialize(opts, &block) configure_declared_params end - def api_configuration - @api.api_configuration.evaluate + def configuration + @api.configuration.evaluate end # @return [Boolean] whether or not this entire scope needs to be diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index d810aff9a..e9a080331 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -63,7 +63,7 @@ def app end end - describe 'with dynamic api_configuration' do + describe 'with dynamic configuration' do context 'when mounting an endpoint conditional on a configuration' do subject(:a_remounted_api) do Class.new(Grape::API) do @@ -71,7 +71,7 @@ def app 'success' end - conditional on: api_configuration[:mount_sometimes] do + conditional on: configuration[:mount_sometimes] do get 'sometimes' do 'sometimes' end @@ -105,7 +105,7 @@ def app end on_mounted do - api_configuration[:endpoints].each do |endpoint_name, endpoint_response| + configuration[:endpoints].each do |endpoint_name, endpoint_response| get endpoint_name do endpoint_response end @@ -121,10 +121,10 @@ def app end end - context 'when the api_configuration is part of the arguments of a method' do + context 'when the configuration is part of the arguments of a method' do subject(:a_remounted_api) do Class.new(Grape::API) do - get api_configuration[:endpoint_name] do + get configuration[:endpoint_name] do 'success' end end @@ -143,22 +143,22 @@ def app expect(last_response.body).to eq 'success' end - context 'when the api_configuration is the value in a key-arg pair' do + context 'when the configuration is the value in a key-arg pair' do subject(:a_remounted_api) do Class.new(Grape::API) do - version 'v1', using: :param, parameter: api_configuration[:version_param] + version 'v1', using: :param, parameter: configuration[:version_param] get 'endpoint' do 'version 1' end - version 'v2', using: :param, parameter: api_configuration[:version_param] + version 'v2', using: :param, parameter: configuration[:version_param] get 'endpoint' do 'version 2' end end end - it 'takes the param from the api_configuration' do + it 'takes the param from the configuration' do root_api.mount a_remounted_api, with: { version_param: 'param_name' } get '/endpoint?param_name=v1' @@ -177,7 +177,7 @@ def app subject(:a_remounted_api) do Class.new(Grape::API) do desc 'The description of this' do - tags ['not_configurable_tag', api_configuration[:a_configurable_tag]] + tags ['not_configurable_tag', configuration[:a_configurable_tag]] end get 'location' do 'success' @@ -194,7 +194,7 @@ def app subject(:a_remounted_api) do Class.new(Grape::API) do params do - requires api_configuration[:required_param], type: api_configuration[:required_type] + requires configuration[:required_param], type: configuration[:required_type] end get 'location' do @@ -224,7 +224,7 @@ def app subject(:a_remounted_api) do Class.new(Grape::API) do params do - optional :restricted_values, values: -> { [api_configuration[:allowed_value], 'always'] } + optional :restricted_values, values: -> { [configuration[:allowed_value], 'always'] } end get 'location' do @@ -233,7 +233,7 @@ def app end end - it 'can read the api_configuration on lambdas' do + it 'can read the configuration on lambdas' do root_api.mount a_remounted_api, with: { allowed_value: 'sometimes' } get '/location', restricted_values: 'always' expect(last_response.body).to eq 'success' @@ -245,10 +245,10 @@ def app end end - context 'when the api_configuration is read within a namespace' do + context 'when the configuration is read within a namespace' do before do a_remounted_api.namespace 'api' do - get "/#{api_configuration[:path]}" do + get "/#{configuration[:path]}" do '10 votes' end end @@ -256,7 +256,7 @@ def app root_api.mount a_remounted_api, with: { path: 'scores' } end - it 'will use the dynamic api_configuration on all routes' do + it 'will use the dynamic configuration on all routes' do get 'api/votes' expect(last_response.body).to eql '10 votes' get 'api/scores' @@ -264,12 +264,12 @@ def app end end - context 'when the api_configuration is read in a helper' do + context 'when the configuration is read in a helper' do subject(:a_remounted_api) do Class.new(Grape::API) do helpers do def printed_response - api_configuration[:some_value] + configuration[:some_value] end end @@ -279,7 +279,7 @@ def printed_response end end - it 'will use the dynamic api_configuration on all routes' do + it 'will use the dynamic configuration on all routes' do root_api.mount(a_remounted_api, with: { some_value: 'response value' }) get '/location' @@ -287,16 +287,16 @@ def printed_response end end - context 'when the api_configuration is read within the response block' do + context 'when the configuration is read within the response block' do subject(:a_remounted_api) do Class.new(Grape::API) do get 'location' do - api_configuration[:some_value] + configuration[:some_value] end end end - it 'will use the dynamic api_configuration on all routes' do + it 'will use the dynamic configuration on all routes' do root_api.mount(a_remounted_api, with: { some_value: 'response value' }) get '/location' From dec5b8dbcebd12e3112f1ef108f054f218482926 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Mon, 10 Jun 2019 13:30:03 +0100 Subject: [PATCH 105/290] Renames on_mounted to mounted --- README.md | 2 +- lib/grape/api/instance.rb | 2 +- spec/grape/api_remount_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ebede2857..be5912c33 100644 --- a/README.md +++ b/README.md @@ -456,7 +456,7 @@ If you want a block of logic running every time an endpoint is mounted (within w ```ruby class ConditionalEndpoint::API < Grape::API - on_mounted do + mounted do YourLogger.info "This API was mounted at: #{Time.now}" get configuration[:endpoint_name] do diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 42edc63fc..b195a16e8 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -16,7 +16,7 @@ def conditional(on:, &block) evaluate_as_instance_with_configuration(block) if on && block_given? end - def on_mounted(&block) + def mounted(&block) return if base_instance? evaluate_as_instance_with_configuration(block) end diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index e9a080331..eebc1d6bd 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -104,7 +104,7 @@ def app 'success' end - on_mounted do + mounted do configuration[:endpoints].each do |endpoint_name, endpoint_response| get endpoint_name do endpoint_response From ea4da8c4304b5522cb71c75d5d528347ee80fcf9 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Mon, 10 Jun 2019 14:00:05 +0100 Subject: [PATCH 106/290] Changes conditional to given --- README.md | 4 ++-- lib/grape/api/instance.rb | 4 ++-- spec/grape/api_remount_spec.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be5912c33..611b9de22 100644 --- a/README.md +++ b/README.md @@ -439,11 +439,11 @@ end You can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace) -If you want logic happening conditional on an `configuration`, you can use the helper `conditional`. +If you want logic happening given on an `configuration`, you can use the helper `given`. ```ruby class ConditionalEndpoint::API < Grape::API - conditional on: configuration[:some_setting] do + given configuration[:some_setting] do get 'mount_this_endpoint_conditionally' do configuration[:configurable_response] end diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index b195a16e8..b53e6d6eb 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -12,8 +12,8 @@ class << self attr_reader :base attr_accessor :configuration - def conditional(on:, &block) - evaluate_as_instance_with_configuration(block) if on && block_given? + def given(conditional_option, &block) + evaluate_as_instance_with_configuration(block) if conditional_option && block_given? end def mounted(&block) diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index eebc1d6bd..3e95eff3f 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -71,7 +71,7 @@ def app 'success' end - conditional on: configuration[:mount_sometimes] do + given configuration[:mount_sometimes] do get 'sometimes' do 'sometimes' end From 97492a5e37c66c9b78ab0396300e9bc090d30453 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 13 Jun 2019 11:46:41 +0100 Subject: [PATCH 107/290] Preparing for release, 1.2.4 --- CHANGELOG.md | 11 ++++++++++- README.md | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1656e97..bf42e3268 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,17 @@ -### 1.2.4 (Next) +### 1.2.5 (Next) #### Features * Your contribution here. + +#### Fixes + +* Your contribution here. + +### 1.2.4 (2019/06/13) + +#### Features + * [#1888](https://github.com/ruby-grape/grape/pull/1888): Makes the `configuration` hash widly available - [@myxoh](https://github.com/myxoh). * [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh). * [#1869](https://github.com/ruby-grape/grape/pull/1869): Fix issue with empty headers after `error!` method call - [@anaumov](https://github.com/anaumov). diff --git a/README.md b/README.md index 611b9de22..08ae32883 100644 --- a/README.md +++ b/README.md @@ -153,9 +153,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.4**. +You're reading the documentation for the next release of Grape, which should be **1.2.5**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.2.3](https://github.com/ruby-grape/grape/blob/v1.2.3/README.md). +The current stable release is [1.2.4](https://github.com/ruby-grape/grape/blob/v1.2.4/README.md). ## Project Resources From 52f38cd6d4bf2c98ec330d876da2f8ba3e59db7e Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 13 Jun 2019 11:51:33 +0100 Subject: [PATCH 108/290] Preparing for next development iteration, 1.2.5 --- lib/grape/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 8b97336b7..882309c43 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.2.4'.freeze + VERSION = '1.2.5'.freeze end From 164f64fb9f92440256787ba30af39b4cdc5b4617 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 5 Jul 2019 17:18:22 +0100 Subject: [PATCH 109/290] Makes sure Grape::API behaves as a Rack::App (#1893) Makes sure Grape::API behaves as a Rack::App by calling: 'call' on the first mounted instance rather than the base instance (which is never mounted) which will have the environment information as to where it was mounted. --- CHANGELOG.md | 1 + lib/grape/api.rb | 15 ++++++++++++++- spec/grape/integration/rack_spec.rb | 22 ++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf42e3268..c4ea50fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). ### 1.2.4 (2019/06/13) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index d4b70ae3e..a830bb834 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -48,7 +48,12 @@ def override_all_methods! # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. # NOTE: This will only be called on an API directly mounted on RACK def call(*args, &block) - base_instance.call(*args, &block) + instance_for_rack = if never_mounted? + base_instance + else + mounted_instances.first + end + instance_for_rack.call(*args, &block) end # Allows an API to itself be inheritable: @@ -147,6 +152,14 @@ def evaluate_arguments(configuration, *args) end end end + + def never_mounted? + mounted_instances.empty? + end + + def mounted_instances + instances - [base_instance] + end end end end diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index e7c9f5665..a1e49a6d8 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -31,4 +31,26 @@ input.unlink end end + + context 'when the app is mounted' do + def app + @main_app ||= Class.new(Grape::API) do + get 'ping' + end + end + + let!(:base) do + app_to_mount = app + Class.new(Grape::API) do + namespace 'namespace' do + mount app_to_mount + end + end + end + + it 'finds the app on the namespace' do + get '/namespace/ping' + expect(last_response.status).to eq 200 + end + end end From 57cb45d314339f36d403b7c1658a6417fcae8fae Mon Sep 17 00:00:00 2001 From: Jason Cooke Date: Thu, 11 Jul 2019 09:38:47 +1200 Subject: [PATCH 110/290] docs: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08ae32883..358c9aea6 100644 --- a/README.md +++ b/README.md @@ -2489,7 +2489,7 @@ class Twitter::API < Grape::API end ``` -Here `'inner'` will be result of handling occured `ArgumentError`. +Here `'inner'` will be result of handling occurred `ArgumentError`. #### Unrescuable Exceptions From 549e1bd84bd39dd63357d12a90a39d23b900279f Mon Sep 17 00:00:00 2001 From: Jean-Francis Bastien Date: Wed, 24 Jul 2019 14:04:15 -0400 Subject: [PATCH 111/290] Refactor ValidatorFactory to use class method. And other minor memory leak fix --- CHANGELOG.md | 1 + lib/grape/dsl/validations.rb | 7 ++++--- lib/grape/endpoint.rb | 4 ++-- lib/grape/validations/params_scope.rb | 22 +++++++++++----------- lib/grape/validations/validator_factory.rb | 17 ++++++----------- 5 files changed, 24 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ea50fdb..677f60276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Your contribution here. * [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). +* [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor ValidatorFactory to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). ### 1.2.4 (2019/06/13) diff --git a/lib/grape/dsl/validations.rb b/lib/grape/dsl/validations.rb index 56208fac4..d705ab865 100644 --- a/lib/grape/dsl/validations.rb +++ b/lib/grape/dsl/validations.rb @@ -27,10 +27,11 @@ def document_attribute(names, opts) setting = description_field(:params) setting ||= description_field(:params, {}) Array(names).each do |name| - setting[name[:full_name].to_s] ||= {} - setting[name[:full_name].to_s].merge!(opts) + full_name_string = name[:full_name].to_s + setting[full_name_string] ||= {} + setting[full_name_string].merge!(opts) - namespace_stackable(:params, name[:full_name].to_s => opts) + namespace_stackable(:params, full_name_string => opts) end end end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 574cb8d8f..12b2150ff 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -353,7 +353,7 @@ def lazy_initialize! def run_validators(validator_factories, request) validation_errors = [] - validators = validator_factories.map(&:create_validator) + validators = validator_factories.map { |options| Grape::Validations::ValidatorFactory.create_validator(options) } ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do validators.each do |validator| @@ -363,7 +363,7 @@ def run_validators(validator_factories, request) validation_errors << e break if validator.fail_fast? rescue Grape::Exceptions::ValidationArrayErrors => e - validation_errors += e.errors + validation_errors.concat e.errors break if validator.fail_fast? end end diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 728901139..1dc23cce0 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -76,7 +76,7 @@ def meets_dependency?(params, request_params) def full_name(name, index: nil) if nested? # Find our containing element's name, and append ours. - [@parent.full_name(@element), [@index || index, name].map(&method(:brackets))].compact.join + "#{@parent.full_name(@element)}#{brackets(@index || index)}#{brackets(name)}" elsif lateral? # Find the name of the element as if it was at the same nesting level # as our parent. We need to forward our index upward to achieve this. @@ -184,14 +184,12 @@ def new_scope(attrs, optional = false, &block) raise Grape::Exceptions::UnsupportedGroupTypeError.new unless Grape::Validations::Types.group?(type) end - opts = attrs[1] || { type: Array } - self.class.new( api: @api, element: attrs.first, parent: self, optional: optional, - type: opts[:type], + type: type || Array, &block ) end @@ -412,13 +410,15 @@ def validate(type, options, attrs, doc_attrs, opts) raise Grape::Exceptions::UnknownValidator.new(type) unless validator_class - factory = Grape::Validations::ValidatorFactory.new(attributes: attrs, - options: options, - required: doc_attrs[:required], - params_scope: self, - opts: opts, - validator_class: validator_class) - @api.namespace_stackable(:validations, factory) + validator_options = { + attributes: attrs, + options: options, + required: doc_attrs[:required], + params_scope: self, + opts: opts, + validator_class: validator_class + } + @api.namespace_stackable(:validations, validator_options) end def validate_value_coercion(coerce_type, *values_list) diff --git a/lib/grape/validations/validator_factory.rb b/lib/grape/validations/validator_factory.rb index 996e3f065..6e2f892a9 100644 --- a/lib/grape/validations/validator_factory.rb +++ b/lib/grape/validations/validator_factory.rb @@ -1,17 +1,12 @@ module Grape module Validations class ValidatorFactory - def initialize(**options) - @validator_class = options.delete(:validator_class) - @options = options - end - - def create_validator - @validator_class.new(@options[:attributes], - @options[:options], - @options[:required], - @options[:params_scope], - @options[:opts]) + def self.create_validator(**options) + options.delete(:validator_class).new(options[:attributes], + options[:options], + options[:required], + options[:params_scope], + options[:opts]) end end end From d774a3e603435caeec9f3b3d7583b28b46278190 Mon Sep 17 00:00:00 2001 From: Jean-Francis Bastien Date: Thu, 25 Jul 2019 08:23:50 -0400 Subject: [PATCH 112/290] Rename full_name variable --- lib/grape/dsl/validations.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/grape/dsl/validations.rb b/lib/grape/dsl/validations.rb index d705ab865..91f98899c 100644 --- a/lib/grape/dsl/validations.rb +++ b/lib/grape/dsl/validations.rb @@ -27,11 +27,11 @@ def document_attribute(names, opts) setting = description_field(:params) setting ||= description_field(:params, {}) Array(names).each do |name| - full_name_string = name[:full_name].to_s - setting[full_name_string] ||= {} - setting[full_name_string].merge!(opts) + full_name = name[:full_name].to_s + setting[full_name] ||= {} + setting[full_name].merge!(opts) - namespace_stackable(:params, full_name_string => opts) + namespace_stackable(:params, full_name => opts) end end end From bcba3acb0cbc5c90530b4c817522614a7f3d7cb0 Mon Sep 17 00:00:00 2001 From: Jean-Francis Bastien Date: Thu, 25 Jul 2019 13:32:59 -0400 Subject: [PATCH 113/290] Read options validator_class in ValidatorFactory --- lib/grape/validations/validator_factory.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/grape/validations/validator_factory.rb b/lib/grape/validations/validator_factory.rb index 6e2f892a9..fe849cc79 100644 --- a/lib/grape/validations/validator_factory.rb +++ b/lib/grape/validations/validator_factory.rb @@ -2,11 +2,11 @@ module Grape module Validations class ValidatorFactory def self.create_validator(**options) - options.delete(:validator_class).new(options[:attributes], - options[:options], - options[:required], - options[:params_scope], - options[:opts]) + options[:validator_class].new(options[:attributes], + options[:options], + options[:required], + options[:params_scope], + options[:opts]) end end end From aadbd0d47147044effd1bc023d28015dc5142661 Mon Sep 17 00:00:00 2001 From: Jean-Francis Bastien Date: Tue, 13 Aug 2019 10:37:57 -0400 Subject: [PATCH 114/290] Define Boolean for Grape::API::Instance (#1900) --- CHANGELOG.md | 3 +- lib/grape/validations/validators/coerce.rb | 4 ++ .../api/defines_boolean_in_params_spec.rb | 37 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 spec/grape/api/defines_boolean_in_params_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 677f60276..9f25422e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ * Your contribution here. * [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). -* [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor ValidatorFactory to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). +* [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor `ValidatorFactory` to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). +* [#1900](https://github.com/ruby-grape/grape/pull/1900): Define boolean for `Grape::Api::Instance` - [@Bhacaz](https://github.com/Bhacaz). ### 1.2.4 (2019/06/13) diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 4d1a32e95..4638d67f4 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -1,6 +1,10 @@ module Grape class API Boolean = Virtus::Attribute::Boolean + + class Instance + Boolean = Virtus::Attribute::Boolean + end end module Validations diff --git a/spec/grape/api/defines_boolean_in_params_spec.rb b/spec/grape/api/defines_boolean_in_params_spec.rb new file mode 100644 index 000000000..166b2d0a8 --- /dev/null +++ b/spec/grape/api/defines_boolean_in_params_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Grape::API::Instance do + describe 'boolean constant' do + module DefinesBooleanInstanceSpec + class API < Grape::API + params do + requires :message, type: Boolean + end + post :echo do + { class: params[:message].class.name, value: params[:message] } + end + end + end + + def app + DefinesBooleanInstanceSpec::API + end + + let(:expected_body) do + { class: 'TrueClass', value: true }.to_s + end + + it 'sets Boolean as a Virtus::Attribute::Boolean' do + post '/echo?message=true' + expect(last_response.status).to eq(201) + expect(last_response.body).to eq expected_body + end + + context 'Params endpoint type' do + subject { DefinesBooleanInstanceSpec::API.new.router.map['POST'].first.options[:params]['message'][:type] } + it 'params type is a Virtus::Attribute::Boolean' do + is_expected.to eq 'Virtus::Attribute::Boolean' + end + end + end +end From c57d97c792ed5b013757c4d5219434f0f20681f0 Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Fri, 30 Aug 2019 14:30:45 +0200 Subject: [PATCH 115/290] Fix Hash params renaming --- CHANGELOG.md | 1 + lib/grape/validations/params_scope.rb | 2 +- spec/grape/validations/params_scope_spec.rb | 13 +++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f25422e1..862e7894c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). * [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor `ValidatorFactory` to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). * [#1900](https://github.com/ruby-grape/grape/pull/1900): Define boolean for `Grape::Api::Instance` - [@Bhacaz](https://github.com/Bhacaz). +* [#1903](https://github.com/ruby-grape/grape/pull/1903): Allow nested params renaming (Hash/Array) - [@bikolya](https://github.com/bikolya). ### 1.2.4 (2019/06/13) diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 1dc23cce0..75a287c7f 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -186,7 +186,7 @@ def new_scope(attrs, optional = false, &block) self.class.new( api: @api, - element: attrs.first, + element: attrs[1][:as] || attrs.first, parent: self, optional: optional, type: type || Array, diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 52ff97a3c..3658efb87 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -165,6 +165,19 @@ def initialize(value) expect(last_response.status).to eq(200) end + + it do + subject.params do + requires :foo, as: :baz, type: Hash do + requires :bar, as: :qux + end + end + subject.get('/nested-renaming') { declared(params).to_json } + get '/nested-renaming', foo: { bar: 'any' } + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('{"baz":{"qux":"any"}}') + end end context 'array without coerce type explicitly given' do From 45b12881f0a886f33d99f2f1d942a1ce9ef81171 Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Fri, 6 Sep 2019 14:21:22 +0100 Subject: [PATCH 116/290] [AutoLoad] Autoload Fixes (#1904) * [Autoload] allows all bits of Grape to be autoloaded * [AutoLoad] creates a compile! method to pre load an API class * [AutoLoad] Addresses rubocop rules, and adds a readme line * [AutoLoad] Adds changes to changelog * Addresses issues with danger * Makes sure the check happens inside the Mutex * Syntax on the README * [AutoLoad] does not take the lock to try to compile is an instance is already present * [AutoLoad] Tests the new methods compile! and instance_for_rack --- CHANGELOG.md | 1 + README.md | 19 +++++ lib/grape.rb | 156 ++++++++++++++++++++++---------------- lib/grape/api.rb | 20 +++-- lib/grape/api/instance.rb | 9 ++- lib/grape/eager_load.rb | 18 +++++ spec/grape/api_spec.rb | 34 +++++++++ 7 files changed, 183 insertions(+), 74 deletions(-) create mode 100644 lib/grape/eager_load.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 862e7894c..52a86c4a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh). #### Fixes diff --git a/README.md b/README.md index 358c9aea6..96388ea59 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ - [Installation](#installation) - [Basic Usage](#basic-usage) - [Mounting](#mounting) + - [All](#all) - [Rack](#rack) - [ActiveRecord without Rails](#activerecord-without-rails) - [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks) @@ -261,6 +262,17 @@ end ## Mounting +### All + + +By default Grape will compile the routes on the first route, it is possible to pre-load routes using the `compile!` method. + +```ruby +Twitter::API.compile! +``` + +This can be added to your `config.ru` (if using rackup), `application.rb` (if using rails), or any file that loads your server. + ### Rack The above sample creates a Rack application that can be run from a rackup `config.ru` file @@ -270,6 +282,13 @@ with `rackup`: run Twitter::API ``` +(With pre-loading you can use) + +```ruby +Twitter::API.compile! +run Twitter::API +``` + And would respond to the following routes: GET /api/statuses/public_timeline diff --git a/lib/grape.rb b/lib/grape.rb index b48cdf42e..97f0215b6 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -54,106 +54,124 @@ module Http module Exceptions extend ::ActiveSupport::Autoload - autoload :Base - autoload :Validation - autoload :ValidationArrayErrors - autoload :ValidationErrors - autoload :MissingVendorOption - autoload :MissingMimeType - autoload :MissingOption - autoload :InvalidFormatter - autoload :InvalidVersionerOption - autoload :UnknownValidator - autoload :UnknownOptions - autoload :UnknownParameter - autoload :InvalidWithOptionForRepresent - autoload :IncompatibleOptionValues - autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type' - autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type' - autoload :InvalidMessageBody - autoload :InvalidAcceptHeader - autoload :InvalidVersionHeader - autoload :MethodNotAllowed - autoload :InvalidResponse + eager_autoload do + autoload :Base + autoload :Validation + autoload :ValidationArrayErrors + autoload :ValidationErrors + autoload :MissingVendorOption + autoload :MissingMimeType + autoload :MissingOption + autoload :InvalidFormatter + autoload :InvalidVersionerOption + autoload :UnknownValidator + autoload :UnknownOptions + autoload :UnknownParameter + autoload :InvalidWithOptionForRepresent + autoload :IncompatibleOptionValues + autoload :MissingGroupTypeError, 'grape/exceptions/missing_group_type' + autoload :UnsupportedGroupTypeError, 'grape/exceptions/unsupported_group_type' + autoload :InvalidMessageBody + autoload :InvalidAcceptHeader + autoload :InvalidVersionHeader + autoload :MethodNotAllowed + autoload :InvalidResponse + end end module Extensions extend ::ActiveSupport::Autoload - - autoload :DeepMergeableHash - autoload :DeepSymbolizeHash - autoload :DeepHashWithIndifferentAccess - autoload :Hash - + eager_autoload do + autoload :DeepMergeableHash + autoload :DeepSymbolizeHash + autoload :DeepHashWithIndifferentAccess + autoload :Hash + end module ActiveSupport extend ::ActiveSupport::Autoload - - autoload :HashWithIndifferentAccess + eager_autoload do + autoload :HashWithIndifferentAccess + end end module Hashie extend ::ActiveSupport::Autoload - - autoload :Mash + eager_autoload do + autoload :Mash + end end end module Middleware extend ::ActiveSupport::Autoload - autoload :Base - autoload :Versioner - autoload :Formatter - autoload :Error - autoload :Globals - autoload :Stack + eager_autoload do + autoload :Base + autoload :Versioner + autoload :Formatter + autoload :Error + autoload :Globals + autoload :Stack + end module Auth extend ::ActiveSupport::Autoload - autoload :Base - autoload :DSL - autoload :StrategyInfo - autoload :Strategies + eager_autoload do + autoload :Base + autoload :DSL + autoload :StrategyInfo + autoload :Strategies + end end module Versioner extend ::ActiveSupport::Autoload - autoload :Path - autoload :Header - autoload :Param - autoload :AcceptVersionHeader + eager_autoload do + autoload :Path + autoload :Header + autoload :Param + autoload :AcceptVersionHeader + end end end module Util extend ::ActiveSupport::Autoload - autoload :InheritableValues - autoload :StackableValues - autoload :ReverseStackableValues - autoload :InheritableSetting - autoload :StrictHashConfiguration - autoload :Registrable + eager_autoload do + autoload :InheritableValues + autoload :StackableValues + autoload :ReverseStackableValues + autoload :InheritableSetting + autoload :StrictHashConfiguration + autoload :Registrable + end end module ErrorFormatter extend ::ActiveSupport::Autoload - autoload :Base - autoload :Json - autoload :Txt - autoload :Xml + eager_autoload do + autoload :Base + autoload :Json + autoload :Txt + autoload :Xml + end end module Formatter extend ::ActiveSupport::Autoload - autoload :Json - autoload :SerializableHash - autoload :Txt - autoload :Xml + eager_autoload do + autoload :Json + autoload :SerializableHash + autoload :Txt + autoload :Xml + end end module Parser extend ::ActiveSupport::Autoload - autoload :Json - autoload :Xml + eager_autoload do + autoload :Json + autoload :Xml + end end module DSL @@ -177,19 +195,25 @@ module DSL class API extend ::ActiveSupport::Autoload - autoload :Helpers + eager_autoload do + autoload :Helpers + end end module Presenters extend ::ActiveSupport::Autoload - autoload :Presenter + eager_autoload do + autoload :Presenter + end end module ServeFile extend ::ActiveSupport::Autoload - autoload :FileResponse - autoload :FileBody - autoload :SendfileResponse + eager_autoload do + autoload :FileResponse + autoload :FileBody + autoload :SendfileResponse + end end end diff --git a/lib/grape/api.rb b/lib/grape/api.rb index a830bb834..0387bac9c 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -6,7 +6,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration]).freeze + NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze class << self attr_accessor :base_instance, :instances @@ -48,11 +48,6 @@ def override_all_methods! # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. # NOTE: This will only be called on an API directly mounted on RACK def call(*args, &block) - instance_for_rack = if never_mounted? - base_instance - else - mounted_instances.first - end instance_for_rack.call(*args, &block) end @@ -111,8 +106,21 @@ def method_missing(method, *args, &block) end end + def compile! + require 'grape/eager_load' + instance_for_rack.compile! # See API::Instance.compile! + end + private + def instance_for_rack + if never_mounted? + base_instance + else + mounted_instances.first + end + end + # Adds a new stage to the set up require to get a Grape::API up and running def add_setup(method, *args, &block) setup_step = { method: method, args: args, block: block } diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index b53e6d6eb..0d3d43d21 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -61,7 +61,7 @@ def change! # the headers, and the body. See [the rack specification] # (http://www.rubydoc.info/github/rack/rack/master/file/SPEC) for more. def call(env) - LOCK.synchronize { compile } unless instance + compile! call!(env) end @@ -79,9 +79,14 @@ def cascade(value = nil) end end + def compile! + return if instance + LOCK.synchronize { compile unless instance } + end + # see Grape::Router#recognize_path def recognize_path(path) - LOCK.synchronize { compile } unless instance + compile! instance.router.recognize_path(path) end diff --git a/lib/grape/eager_load.rb b/lib/grape/eager_load.rb new file mode 100644 index 000000000..933c97908 --- /dev/null +++ b/lib/grape/eager_load.rb @@ -0,0 +1,18 @@ +Grape.eager_load! +Grape::Http.eager_load! +Grape::Exceptions.eager_load! +Grape::Extensions.eager_load! +Grape::Extensions::ActiveSupport.eager_load! +Grape::Extensions::Hashie.eager_load! +Grape::Middleware.eager_load! +Grape::Middleware::Auth.eager_load! +Grape::Middleware::Versioner.eager_load! +Grape::Util.eager_load! +Grape::ErrorFormatter.eager_load! +Grape::Formatter.eager_load! +Grape::Parser.eager_load! +Grape::DSL.eager_load! +Grape::API.eager_load! +Grape::Presenters.eager_load! +Grape::ServeFile.eager_load! +Rack::Head # AutoLoads the Rack::Head diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 889dd675e..2dca91889 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -885,6 +885,40 @@ class DummyFormatClass end end + describe '.compile!' do + it 'requires the grape/eager_load file' do + expect(app).to receive(:require).with('grape/eager_load') { nil } + app.compile! + end + + it 'compiles the instance for rack!' do + stubbed_object = double(:instance_for_rack) + allow(app).to receive(:instance_for_rack) { stubbed_object } + end + end + + # NOTE: this method is required to preserve the ability of pre-mounting + # the root API into a namespace, it may be deprecated in the future. + describe 'instance_for_rack' do + context 'when the app was not mounted' do + it 'returns the base_instance' do + expect(app.send(:instance_for_rack)).to eq app.base_instance + end + end + + context 'when the app was mounted' do + it 'returns the first mounted instance' do + mounted_app = app + Class.new(Grape::API) do + namespace 'new_namespace' do + mount mounted_app + end + end + expect(app.send(:instance_for_rack)).to eq app.send(:mounted_instances).first + end + end + end + describe 'filters' do it 'adds a before filter' do subject.before { @foo = 'first' } From ddc4805c544452d96aa37b5e351d0c8f83179cfb Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 1 Oct 2019 20:38:09 +0300 Subject: [PATCH 117/290] AtLeastOneOfValidator properly treats nested params in errors Unlike other validators `Grape::Valiations::AtLeastOneOfValidator` didn't wrap params into a scope (namespace) for errors when params were nested. --- CHANGELOG.md | 3 ++- .../validations/validators/at_least_one_of.rb | 7 ++++-- .../validators/at_least_one_of_spec.rb | 18 +++++++++++---- spec/grape/validations_spec.rb | 22 +++++++++---------- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a86c4a8..07088a957 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ #### Fixes * Your contribution here. +* [#1911](https://github.com/ruby-grape/grape/pull/1911): Make sure `Grape::Valiations::AtLeastOneOfValidator` properly treats nested params in errors - [@dnesteryuk](https://github.com/dnesteryuk). * [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). * [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor `ValidatorFactory` to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). * [#1900](https://github.com/ruby-grape/grape/pull/1900): Define boolean for `Grape::Api::Instance` - [@Bhacaz](https://github.com/Bhacaz). @@ -17,7 +18,7 @@ #### Features -* [#1888](https://github.com/ruby-grape/grape/pull/1888): Makes the `configuration` hash widly available - [@myxoh](https://github.com/myxoh). +* [#1888](https://github.com/ruby-grape/grape/pull/1888): Makes the `configuration` hash widely available - [@myxoh](https://github.com/myxoh). * [#1864](https://github.com/ruby-grape/grape/pull/1864): Adds `finally` on the API - [@myxoh](https://github.com/myxoh). * [#1869](https://github.com/ruby-grape/grape/pull/1869): Fix issue with empty headers after `error!` method call - [@anaumov](https://github.com/anaumov). diff --git a/lib/grape/validations/validators/at_least_one_of.rb b/lib/grape/validations/validators/at_least_one_of.rb index 077393b63..cfe006c89 100644 --- a/lib/grape/validations/validators/at_least_one_of.rb +++ b/lib/grape/validations/validators/at_least_one_of.rb @@ -1,11 +1,14 @@ +require 'grape/validations/validators/multiple_params_base' + module Grape module Validations - require 'grape/validations/validators/multiple_params_base' class AtLeastOneOfValidator < MultipleParamsBase def validate!(params) super if scope_requires_params && no_exclusive_params_are_present - raise Grape::Exceptions::Validation, params: all_keys, message: message(:at_least_one) + scoped_params = all_keys.map { |key| @scope.full_name(key) } + raise Grape::Exceptions::Validation, params: scoped_params, + message: message(:at_least_one) end params end diff --git a/spec/grape/validations/validators/at_least_one_of_spec.rb b/spec/grape/validations/validators/at_least_one_of_spec.rb index 7a331fe9b..17eeb90a1 100644 --- a/spec/grape/validations/validators/at_least_one_of_spec.rb +++ b/spec/grape/validations/validators/at_least_one_of_spec.rb @@ -9,8 +9,15 @@ def params(arg) end def required?; end + + # mimics a method from Grape::Validations::ParamsScope which decides how a parameter must + # be named in errors + def full_name(name) + "food[#{name}]" + end end end + let(:at_least_one_of_params) { %i[beer wine grapefruit] } let(:validator) { described_class.new(at_least_one_of_params, {}, false, scope.new) } @@ -48,11 +55,14 @@ def required?; end context 'when none of the restricted params is selected' do let(:params) { { somethingelse: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + expected_params = at_least_one_of_params.map { |p| "food[#{p}]" } + + expect { validator.validate! params }.to raise_error do |error| + expect(error).to be_a(Grape::Exceptions::Validation) + expect(error.params).to eq(expected_params) + expect(error.message).to eq(I18n.t('grape.errors.messages.at_least_one')) + end end end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 90e04bafe..dd81fddaa 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -1485,16 +1485,16 @@ def validate_param!(attr_name, params) before :each do subject.params do requires :nested, type: Hash do - optional :beer_nested - optional :wine_nested - optional :juice_nested - at_least_one_of :beer_nested, :wine_nested, :juice_nested + optional :beer + optional :wine + optional :juice + at_least_one_of :beer, :wine, :juice end optional :nested2, type: Array do - optional :beer_nested2 - optional :wine_nested2 - optional :juice_nested2 - at_least_one_of :beer_nested2, :wine_nested2, :juice_nested2 + optional :beer + optional :wine + optional :juice + at_least_one_of :beer, :wine, :juice end end subject.get '/at_least_one_of_nested' do @@ -1505,17 +1505,17 @@ def validate_param!(attr_name, params) it 'errors when none are present' do get '/at_least_one_of_nested' expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'nested is missing, beer_nested, wine_nested, juice_nested are missing, at least one parameter must be provided' + expect(last_response.body).to eq 'nested is missing, nested[beer], nested[wine], nested[juice] are missing, at least one parameter must be provided' end it 'does not error when one is present' do - get '/at_least_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string' }] + get '/at_least_one_of_nested', nested: { beer: 'string' }, nested2: [{ beer: 'string' }] expect(last_response.status).to eq(200) expect(last_response.body).to eq 'at_least_one_of works!' end it 'does not error when two are present' do - get '/at_least_one_of_nested', nested: { beer_nested: 'string', wine_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'string' }] + get '/at_least_one_of_nested', nested: { beer: 'string', wine: 'string' }, nested2: [{ beer: 'string', wine: 'string' }] expect(last_response.status).to eq(200) expect(last_response.body).to eq 'at_least_one_of works!' end From 6b03904f51ea60c16af004011016b5b64db5f0cb Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 1 Oct 2019 22:07:46 +0300 Subject: [PATCH 118/290] upgrade rack-test to 1.1.0 --- Gemfile | 2 +- gemfiles/multi_json.gemfile | 2 +- gemfiles/multi_xml.gemfile | 2 +- gemfiles/rack_edge.gemfile | 2 +- gemfiles/rails_3.gemfile | 2 +- gemfiles/rails_4.gemfile | 2 +- gemfiles/rails_5.gemfile | 2 +- gemfiles/rails_edge.gemfile | 2 +- spec/grape/validations/validators/coerce_spec.rb | 4 ++-- spec/grape/validations_spec.rb | 14 ++++++++++++-- 10 files changed, 22 insertions(+), 12 deletions(-) diff --git a/Gemfile b/Gemfile index 2881582d8..b088fc793 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index 4ab020140..a8b1573a2 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index c093022a6..4ade375c4 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index eb276ec2b..4065843b5 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/rails_3.gemfile b/gemfiles/rails_3.gemfile index c6445ee6a..0ac3c18e8 100644 --- a/gemfiles/rails_3.gemfile +++ b/gemfiles/rails_3.gemfile @@ -28,7 +28,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/rails_4.gemfile b/gemfiles/rails_4.gemfile index 2f331cb86..24c711230 100644 --- a/gemfiles/rails_4.gemfile +++ b/gemfiles/rails_4.gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index c8c38fbb4..80f19f16a 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index 56ecdf5f0..369f50e80 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -27,7 +27,7 @@ group :test do gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 0.6.3' + gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.1.0', require: false end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index bf4a5cc8a..3cf65ace4 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -576,7 +576,7 @@ def self.parsed?(value) expect(last_response.status).to eq(200) expect(last_response.body).to eq('arrays work') - get '/', splines: [{ x: 2, ints: [] }, { x: 3, ints: [4], obj: { y: 'quack' } }] + get '/', splines: [{ x: 2, ints: [5] }, { x: 3, ints: [4], obj: { y: 'quack' } }] expect(last_response.status).to eq(200) expect(last_response.body).to eq('arrays work') @@ -592,7 +592,7 @@ def self.parsed?(value) expect(last_response.status).to eq(400) expect(last_response.body).to eq('splines[x] does not have a valid value') - get '/', splines: [{ x: 1, ints: [] }, { x: 4, ints: [] }] + get '/', splines: [{ x: 1, ints: [5] }, { x: 4, ints: [6] }] expect(last_response.status).to eq(400) expect(last_response.body).to eq('splines[x] does not have a valid value') end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 90e04bafe..0a3217848 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -540,7 +540,10 @@ def validate_param!(attr_name, params) ] expect(last_response.status).to eq(400) - expect(last_response.body).to eq('children[0][parents] is missing, children[1][parents] is missing') + expect(last_response.body).to eq( + 'children[0][parents][0][name] is missing, ' \ + 'children[1][parents][0][name] is missing' + ) end it 'safely handles empty arrays and blank parameters' do @@ -548,7 +551,14 @@ def validate_param!(attr_name, params) # should actually return 200, since an empty array is valid. get '/within_array', children: [] expect(last_response.status).to eq(400) - expect(last_response.body).to eq('children is missing') + expect(last_response.body).to eq( + 'children[0][name] is missing, ' \ + 'children[0][parents] is missing, ' \ + 'children[0][parents] is invalid, ' \ + 'children[0][parents][0][name] is missing, ' \ + 'children[0][parents][0][name] is empty' + ) + get '/within_array', children: [name: 'Jay'] expect(last_response.status).to eq(400) expect(last_response.body).to eq('children[0][parents] is missing') From 2b7be0cd914d74bfc2e3296bf3614b9b0425d550 Mon Sep 17 00:00:00 2001 From: unleashy Date: Wed, 2 Oct 2019 11:42:18 -0300 Subject: [PATCH 119/290] Add .configuration to Grape::API (fixes #1906) (#1907) --- CHANGELOG.md | 1 + README.md | 11 +++++++++++ lib/grape/api.rb | 15 +++++++++++++++ spec/grape/api_spec.rb | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a86c4a8..936ca5ee9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Your contribution here. * [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh). +* [#1907](https://github.com/ruby-grape/grape/pull/1907): Adds outside configuration to Grape with `configure` - [@unleashy](https://github.com/unleashy). #### Fixes diff --git a/README.md b/README.md index 96388ea59..ebaf1ac83 100644 --- a/README.md +++ b/README.md @@ -631,6 +631,17 @@ Grape.configure do |config| end ``` +You can also configure a single API: + +```ruby +API.configure do |config| + config[key] = value +end +``` + +This will be available inside the API with `configuration`, as if it were +[mount configuration](#mount-configuration). + ## Parameters Request parameters are available through the `params` hash object. This includes `GET`, `POST` diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 0387bac9c..fa707d2e9 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -42,6 +42,21 @@ def override_all_methods! end end + # Configure an API from the outside. If a block is given, it'll pass a + # configuration hash to the block which you can use to configure your + # API. If no block is given, returns the configuration hash. + # The configuration set here is accessible from inside an API with + # `configuration` as normal. + def configure + config = @base_instance.configuration + if block_given? + yield config + self + else + config + end + end + # This is the interface point between Rack and Grape; it accepts a request # from Rack and ultimately returns an array of three values: the status, # the headers, and the body. See [the rack specification] diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 2dca91889..63c1623bb 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -3754,6 +3754,44 @@ def before end end + describe '.configure' do + context 'when given a block' do + it 'returns self' do + expect(subject.configure {}).to be subject + end + + it 'calls the block passing the config' do + call = [false, nil] + subject.configure do |config| + call = [true, config] + end + + expect(call[0]).to be true + expect(call[1]).not_to be_nil + end + end + + context 'when not given a block' do + it 'returns a configuration object' do + expect(subject.configure).to respond_to(:[], :[]=) + end + end + + it 'allows configuring the api' do + subject.configure do |config| + config[:hello] = 'hello' + config[:bread] = 'bread' + end + + subject.get '/hello-bread' do + "#{configuration[:hello]} #{configuration[:bread]}" + end + + get '/hello-bread' + expect(last_response.body).to eq 'hello bread' + end + end + context 'catch-all' do before do api1 = Class.new(Grape::API) From 137d4741e2c60a25ad83c4754d969c3743a5cbf1 Mon Sep 17 00:00:00 2001 From: Peter Leitzen Date: Fri, 4 Oct 2019 21:07:43 +0200 Subject: [PATCH 120/290] Run specs in random order This commit also restores global state in couple of specs which caused flaky failures after enabling randomization. --- .rspec | 1 + CHANGELOG.md | 1 + spec/grape/endpoint_spec.rb | 2 +- spec/grape/exceptions/base_spec.rb | 4 ++++ spec/grape/middleware/formatter_spec.rb | 4 ++++ spec/grape/validations/validators/coerce_spec.rb | 4 ++++ 6 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.rspec b/.rspec index d3ad5a94f..87d9ba441 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color --format=documentation +--order=rand diff --git a/CHANGELOG.md b/CHANGELOG.md index f6eb770b6..a6c491716 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Your contribution here. * [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh). * [#1907](https://github.com/ruby-grape/grape/pull/1907): Adds outside configuration to Grape with `configure` - [@unleashy](https://github.com/unleashy). +* [#1914](https://github.com/ruby-grape/grape/pull/1914): Run specs in random order - [@splattael](https://github.com/splattael). #### Fixes diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 4bf327f3f..91a900a64 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -8,7 +8,7 @@ def app end describe '.before_each' do - after { Grape::Endpoint.before_each(nil) } + after { Grape::Endpoint.before_each.clear } it 'is settable via block' do block = ->(_endpoint) { 'noop' } diff --git a/spec/grape/exceptions/base_spec.rb b/spec/grape/exceptions/base_spec.rb index e793740f3..f95531f7a 100644 --- a/spec/grape/exceptions/base_spec.rb +++ b/spec/grape/exceptions/base_spec.rb @@ -8,8 +8,11 @@ let(:attributes) { { klass: String, to_format: 'xml' } } after do + I18n.enforce_available_locales = true I18n.available_locales = %i[en] + I18n.locale = :en I18n.default_locale = :en + I18n.reload! end context 'when I18n enforces available locales' do @@ -29,6 +32,7 @@ context 'when the fallback locale is not available' do before do I18n.available_locales = %i[de jp] + I18n.locale = :de I18n.default_locale = :de end diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index a4b46acdf..be85d4bd2 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -391,6 +391,10 @@ def self.call(_, _) Grape::Formatter.register :invalid, InvalidFormatter Grape::ContentTypes::CONTENT_TYPES[:invalid] = 'application/x-invalid' end + after do + Grape::ContentTypes::CONTENT_TYPES.delete(:invalid) + Grape::Formatter.default_elements.delete(:invalid) + end it 'returns response by invalid formatter' do env = { 'PATH_INFO' => '/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid' } diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 3cf65ace4..94f66e873 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -20,10 +20,13 @@ class User context 'i18n' do after :each do + I18n.available_locales = %i[en] I18n.locale = :en + I18n.default_locale = :en end it 'i18n error on malformed input' do + I18n.available_locales = %i[en zh-CN] I18n.load_path << File.expand_path('../zh-CN.yml', __FILE__) I18n.reload! I18n.locale = 'zh-CN'.to_sym @@ -40,6 +43,7 @@ class User end it 'gives an english fallback error when default locale message is blank' do + I18n.available_locales = %i[en pt-BR] I18n.locale = 'pt-BR'.to_sym subject.params do requires :age, type: Integer From 4905995b4f739c004c9a300eccd84ef8537d0b57 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 5 Oct 2019 15:22:19 +0300 Subject: [PATCH 121/290] micro optimizations in allocating hashes and arrays There were a few places where hashes could be mutated without side effects, they already work with copies of original hashes. So, `merge!` replaced some of `merge` calls. Also, `Grape::Util::StackableValues`, `Grape::Util::ReverseStackableValues` and `Grape::Util::InheritableValues` got a base class which keeps common methods to avoid duplication. Besides, that a `[]` and `keys` methods were optimized to allocate less arrays when it is possible. There is an additional benchmark which measures more complex scenario. Unfortunately, there isn't noticeable difference in performance between the master and this change. However, I also profiled the same endpoint as the benchmark has. Before this change: Measure Mode: memory Thread ID: 47216803207600 Fiber ID: 47216805628400 Total: 40536.000000 Sort by: self_time %self total self wait child calls name 13.85 6848.000 5616.000 0.000 1232.000 15 *Hash#each_pair 9.73 3944.000 3944.000 0.000 0.000 17 Hash#merge 8.80 5088.000 3568.000 0.000 1520.000 8 *Array#map 8.57 11720.000 3472.000 0.000 8248.000 27 *Class#new 4.54 1840.000 1840.000 0.000 0.000 46 Symbol#to_s 4.44 1800.000 1800.000 0.000 0.000 20 *Grape::Util::StackableValues#[] 3.36 1440.000 1360.000 0.000 80.000 5 Grape::Endpoint#run_filters 2.88 3040.000 1168.000 0.000 1872.000 9 *Hash#each 2.37 1200.000 960.000 0.000 240.000 6 #normalize_path 2.29 1088.000 928.000 0.000 160.000 4 ActiveSupport::HashWithIndifferentAccess#[]= 2.21 1128.000 896.000 0.000 232.000 8 *Kernel#dup 2.07 840.000 840.000 0.000 0.000 9 String#split 1.64 12008.000 664.000 0.000 11344.000 1 Grape::Endpoint#run_validators After this change: Measure Mode: memory Thread ID: 47390769740200 Fiber ID: 47390772121400 Total: 37296.000000 Sort by: self_time %self total self wait child calls name 15.06 6848.000 5616.000 0.000 1232.000 15 *Hash#each_pair 9.57 5088.000 3568.000 0.000 1520.000 8 *Array#map 9.31 11720.000 3472.000 0.000 8248.000 27 *Class#new 4.93 1840.000 1840.000 0.000 0.000 46 Symbol#to_s 4.35 1624.000 1624.000 0.000 0.000 7 Hash#merge 3.65 1440.000 1360.000 0.000 80.000 5 Grape::Endpoint#run_filters 3.13 3040.000 1168.000 0.000 1872.000 9 *Hash#each 2.57 1200.000 960.000 0.000 240.000 6 #normalize_path 2.49 1088.000 928.000 0.000 160.000 4 ActiveSupport::HashWithIndifferentAccess#[]= 2.40 1128.000 896.000 0.000 232.000 8 *Kernel#dup 2.25 840.000 840.000 0.000 0.000 9 String#split 2.15 1400.000 800.000 0.000 600.000 20 *Grape::Util::StackableValues#[] 1.78 11768.000 664.000 0.000 11104.000 1 Grape::Endpoint#run_validators - The total memory usage went down from 40536 to 37296 bytes. - `Hash#merge` moved from second position to 5th. - `Grape::Util::StackableValues#[]` moved from 6th to 12th. --- CHANGELOG.md | 1 + benchmark/nested_params.rb | 41 +++++++++++++++++++++ lib/grape/endpoint.rb | 2 +- lib/grape/error_formatter.rb | 2 +- lib/grape/exceptions/validation_errors.rb | 6 ++- lib/grape/formatter.rb | 2 +- lib/grape/parser.rb | 2 +- lib/grape/util/base_inheritable.rb | 34 +++++++++++++++++ lib/grape/util/inheritable_values.rb | 30 +++------------ lib/grape/util/reverse_stackable_values.rb | 43 ++++------------------ lib/grape/util/stackable_values.rb | 41 ++++++++++----------- lib/grape/validations/validators/base.rb | 8 ++-- 12 files changed, 119 insertions(+), 93 deletions(-) create mode 100644 benchmark/nested_params.rb create mode 100644 lib/grape/util/base_inheritable.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index f6eb770b6..a01bdafff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1915](https://github.com/ruby-grape/grape/pull/1915): Micro optimizations in allocating hashes and arrays - [@dnesteryuk](https://github.com/dnesteryuk). * [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh). * [#1907](https://github.com/ruby-grape/grape/pull/1907): Adds outside configuration to Grape with `configure` - [@unleashy](https://github.com/unleashy). diff --git a/benchmark/nested_params.rb b/benchmark/nested_params.rb new file mode 100644 index 000000000..1e4f88133 --- /dev/null +++ b/benchmark/nested_params.rb @@ -0,0 +1,41 @@ +require 'grape' +require 'benchmark/ips' + +class API < Grape::API + prefix :api + version 'v1', using: :path + + params do + requires :address, type: Hash do + requires :street, type: String + requires :postal_code, type: Integer + optional :city, type: String + end + end + post '/' do + 'hello' + end +end + +options = { + method: 'POST', + params: { + address: { + street: 'Alexis Pl.', + postal_code: '90210', + city: 'Beverly Hills' + } + } +} + +env = Rack::MockRequest.env_for('/api/v1', options) + +10.times do |i| + env["HTTP_HEADER#{i}"] = '123' +end + +Benchmark.ips do |ips| + ips.report('POST with nested params') do + API.call env + end +end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 12b2150ff..0551ef88d 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -200,7 +200,7 @@ def prepare_version end def merge_route_options(**default) - options[:route_options].clone.merge(**default) + options[:route_options].clone.merge!(**default) end def map_routes diff --git a/lib/grape/error_formatter.rb b/lib/grape/error_formatter.rb index f63e8e510..85107f6e1 100644 --- a/lib/grape/error_formatter.rb +++ b/lib/grape/error_formatter.rb @@ -14,7 +14,7 @@ def builtin_formatters end def formatters(options) - builtin_formatters.merge(default_elements).merge(options[:error_formatters] || {}) + builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {}) end def formatter_for(api_format, **options) diff --git a/lib/grape/exceptions/validation_errors.rb b/lib/grape/exceptions/validation_errors.rb index 92bf5d590..822a5fce4 100644 --- a/lib/grape/exceptions/validation_errors.rb +++ b/lib/grape/exceptions/validation_errors.rb @@ -39,14 +39,16 @@ def to_json(**_opts) end def full_messages - map { |attributes, error| full_message(attributes, error) }.uniq + messages = map { |attributes, error| full_message(attributes, error) } + messages.uniq! + messages end private def full_message(attributes, error) I18n.t( - 'grape.errors.format'.to_sym, + 'grape.errors.format', default: '%{attributes} %{message}', attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes), message: error.message diff --git a/lib/grape/formatter.rb b/lib/grape/formatter.rb index 5047e722a..ef95fcb7a 100644 --- a/lib/grape/formatter.rb +++ b/lib/grape/formatter.rb @@ -14,7 +14,7 @@ def builtin_formmaters end def formatters(options) - builtin_formmaters.merge(default_elements).merge(options[:formatters] || {}) + builtin_formmaters.merge(default_elements).merge!(options[:formatters] || {}) end def formatter_for(api_format, **options) diff --git a/lib/grape/parser.rb b/lib/grape/parser.rb index 41ad889eb..a1e87bb40 100644 --- a/lib/grape/parser.rb +++ b/lib/grape/parser.rb @@ -12,7 +12,7 @@ def builtin_parsers end def parsers(options) - builtin_parsers.merge(default_elements).merge(options[:parsers] || {}) + builtin_parsers.merge(default_elements).merge!(options[:parsers] || {}) end def parser_for(api_format, **options) diff --git a/lib/grape/util/base_inheritable.rb b/lib/grape/util/base_inheritable.rb new file mode 100644 index 000000000..277c61d5b --- /dev/null +++ b/lib/grape/util/base_inheritable.rb @@ -0,0 +1,34 @@ +module Grape + module Util + # Base for classes which need to operate with own values kept + # in the hash and inherited values kept in a Hash-like object. + class BaseInheritable + attr_accessor :inherited_values + attr_accessor :new_values + + # @param inherited_values [Object] An object implementing an interface + # of the Hash class. + def initialize(inherited_values = {}) + @inherited_values = inherited_values + @new_values = {} + end + + def delete(key) + new_values.delete key + end + + def initialize_copy(other) + super + self.inherited_values = other.inherited_values + self.new_values = other.new_values.dup + end + + def keys + combined = inherited_values.keys + combined.concat(new_values.keys) + combined.uniq! + combined + end + end + end +end diff --git a/lib/grape/util/inheritable_values.rb b/lib/grape/util/inheritable_values.rb index 2546075db..bba7be02b 100644 --- a/lib/grape/util/inheritable_values.rb +++ b/lib/grape/util/inheritable_values.rb @@ -1,14 +1,8 @@ +require_relative 'base_inheritable' + module Grape module Util - class InheritableValues - attr_accessor :inherited_values - attr_accessor :new_values - - def initialize(inherited_values = {}) - self.inherited_values = inherited_values - self.new_values = {} - end - + class InheritableValues < BaseInheritable def [](name) values[name] end @@ -17,26 +11,12 @@ def []=(name, value) new_values[name] = value end - def delete(key) - new_values.delete key - end - def merge(new_hash) - values.merge(new_hash) - end - - def keys - (new_values.keys + inherited_values.keys).sort.uniq + values.merge!(new_hash) end def to_hash - values.clone - end - - def initialize_copy(other) - super - self.inherited_values = other.inherited_values - self.new_values = other.new_values.dup + values end protected diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb index 474325f72..8858c83fe 100644 --- a/lib/grape/util/reverse_stackable_values.rb +++ b/lib/grape/util/reverse_stackable_values.rb @@ -1,45 +1,16 @@ +require_relative 'stackable_values' + module Grape module Util - class ReverseStackableValues - attr_accessor :inherited_values - attr_accessor :new_values - - def initialize(inherited_values = {}) - @inherited_values = inherited_values - @new_values = {} - end + class ReverseStackableValues < StackableValues + protected - def [](name) + def concat_values(inherited_value, new_value) [].tap do |value| - value.concat(@new_values[name] || []) - value.concat(@inherited_values[name] || []) - end - end - - def []=(name, value) - @new_values[name] ||= [] - @new_values[name].push value - end - - def delete(key) - new_values.delete key - end - - def keys - (@new_values.keys + @inherited_values.keys).sort.uniq - end - - def to_hash - keys.each_with_object({}) do |key, result| - result[key] = self[key] + value.concat(new_value) + value.concat(inherited_value) end end - - def initialize_copy(other) - super - self.inherited_values = other.inherited_values - self.new_values = other.new_values.dup - end end end end diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index a6c8179bd..4c97adace 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -1,23 +1,25 @@ +require_relative 'base_inheritable' + module Grape module Util - class StackableValues - attr_accessor :inherited_values - attr_accessor :new_values + class StackableValues < BaseInheritable attr_reader :frozen_values - def initialize(inherited_values = {}) - @inherited_values = inherited_values - @new_values = {} + def initialize(*_args) + super + @frozen_values = {} end def [](name) return @frozen_values[name] if @frozen_values.key? name - value = [] - value.concat(@inherited_values[name] || []) - value.concat(@new_values[name] || []) - value + inherited_value = @inherited_values[name] + new_value = @new_values[name] || [] + + return new_value unless inherited_value + + concat_values(inherited_value, new_value) end def []=(name, value) @@ -26,14 +28,6 @@ def []=(name, value) @new_values[name].push value end - def delete(key) - new_values.delete key - end - - def keys - (@new_values.keys + @inherited_values.keys).sort.uniq - end - def to_hash keys.each_with_object({}) do |key, result| result[key] = self[key] @@ -44,10 +38,13 @@ def freeze_value(key) @frozen_values[key] = self[key].freeze end - def initialize_copy(other) - super - self.inherited_values = other.inherited_values - self.new_values = other.new_values.dup + protected + + def concat_values(inherited_value, new_value) + [].tap do |value| + value.concat(inherited_value) + value.concat(new_value) + end end end end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 169e2155a..c62956acc 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -56,10 +56,10 @@ def validate!(params) def self.convert_to_short_name(klass) ret = klass.name.gsub(/::/, '/') - .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') - .gsub(/([a-z\d])([A-Z])/, '\1_\2') - .tr('-', '_') - .downcase + ret.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + ret.gsub!(/([a-z\d])([A-Z])/, '\1_\2') + ret.tr!('-', '_') + ret.downcase! File.basename(ret, '_validator') end From 87c88e4263e2e12c2bac6ccc02343dd7d8ba4ab4 Mon Sep 17 00:00:00 2001 From: Nikolay Rys Date: Tue, 15 Oct 2019 14:56:52 +0200 Subject: [PATCH 122/290] Drop old appraisals (#1916) --- Appraisals | 9 -------- CHANGELOG.md | 1 + gemfiles/rails_3.gemfile | 36 ----------------------------- gemfiles/rails_4.gemfile | 35 ---------------------------- spec/grape/integration/rack_spec.rb | 6 ----- 5 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 gemfiles/rails_3.gemfile delete mode 100644 gemfiles/rails_4.gemfile diff --git a/Appraisals b/Appraisals index c0d7415fd..1cb41c12b 100644 --- a/Appraisals +++ b/Appraisals @@ -1,12 +1,3 @@ -appraise 'rails-3' do - gem 'rails', '3.2.22.5' - gem 'rack-cache', '<= 1.2' # Pin as next rack-cache version (1.3) removes Ruby1.9 support -end - -appraise 'rails-4' do - gem 'rails', '4.2.10' -end - appraise 'rails-5' do gem 'rails', '5.2.1' end diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a77c3ff..1ccc7aa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ #### Fixes * Your contribution here. +* [#1916](https://github.com/ruby-grape/grape/pull/1916): Drop old appraisals - [@NikolayRys](https://github.com/NikolayRys). * [#1911](https://github.com/ruby-grape/grape/pull/1911): Make sure `Grape::Valiations::AtLeastOneOfValidator` properly treats nested params in errors - [@dnesteryuk](https://github.com/dnesteryuk). * [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). * [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor `ValidatorFactory` to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). diff --git a/gemfiles/rails_3.gemfile b/gemfiles/rails_3.gemfile deleted file mode 100644 index 0ac3c18e8..000000000 --- a/gemfiles/rails_3.gemfile +++ /dev/null @@ -1,36 +0,0 @@ -# This file was generated by Appraisal - -source 'https://rubygems.org' - -gem 'rails', '3.2.22.5' -gem 'rack-cache', '<= 1.2' - -group :development, :test do - gem 'bundler' - gem 'hashie' - gem 'rake' - gem 'rubocop', '0.51.0' -end - -group :development do - gem 'appraisal' - gem 'benchmark-ips' - gem 'guard' - gem 'guard-rspec' - gem 'guard-rubocop' -end - -group :test do - gem 'cookiejar' - gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' - gem 'grape-entity', '~> 0.6' - gem 'maruku' - gem 'mime-types' - gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 1.1.0' - gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false -end - -gemspec path: '../' diff --git a/gemfiles/rails_4.gemfile b/gemfiles/rails_4.gemfile deleted file mode 100644 index 24c711230..000000000 --- a/gemfiles/rails_4.gemfile +++ /dev/null @@ -1,35 +0,0 @@ -# This file was generated by Appraisal - -source 'https://rubygems.org' - -gem 'rails', '4.2.10' - -group :development, :test do - gem 'bundler' - gem 'hashie' - gem 'rake' - gem 'rubocop', '0.51.0' -end - -group :development do - gem 'appraisal' - gem 'benchmark-ips' - gem 'guard' - gem 'guard-rspec' - gem 'guard-rubocop' -end - -group :test do - gem 'cookiejar' - gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' - gem 'grape-entity', '~> 0.6' - gem 'maruku' - gem 'mime-types' - gem 'rack-jsonp', require: 'rack/jsonp' - gem 'rack-test', '~> 1.1.0' - gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false -end - -gemspec path: '../' diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index a1e49a6d8..872f07524 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -19,12 +19,6 @@ } env = Rack::MockRequest.env_for('/', options) - unless RUBY_PLATFORM == 'java' - major, minor, patch = Rack.release.split('.').map(&:to_i) - patch ||= 0 # rack <= 1.5.2 does not specify patch version - pending 'Rack 1.5.3 or 1.6.1 required' unless major >= 2 || (major >= 1 && ((minor == 5 && patch >= 3) || (minor >= 6))) - end - expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test') ensure input.close From 5b5847f2c411f3b0f83e6189bc12aec88e5dfe3f Mon Sep 17 00:00:00 2001 From: Nikolay Rys Date: Wed, 16 Oct 2019 03:26:48 +0200 Subject: [PATCH 123/290] Update how the list of statuses in rack are accessed In rack 2.1.0 the list of statuses has become a hash with codes as keys. More info: https://github.com/rack/rack/commit/6746515453a44cc6268c46d525158278cf96307d --- CHANGELOG.md | 1 + spec/grape/middleware/formatter_spec.rb | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ccc7aa9f..e321bccbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ #### Fixes * Your contribution here. +* [#1917](https://github.com/ruby-grape/grape/pull/1917): Update access to rack constant - [@NikolayRys](https://github.com/NikolayRys). * [#1916](https://github.com/ruby-grape/grape/pull/1916): Drop old appraisals - [@NikolayRys](https://github.com/NikolayRys). * [#1911](https://github.com/ruby-grape/grape/pull/1911): Make sure `Grape::Valiations::AtLeastOneOfValidator` properly treats nested params in errors - [@dnesteryuk](https://github.com/dnesteryuk). * [#1893](https://github.com/ruby-grape/grape/pull/1893): Allows `Grape::API` to behave like a Rack::app in some instances where it was misbehaving - [@myxoh](https://github.com/myxoh). diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index be85d4bd2..f3bc98b91 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -213,7 +213,13 @@ def to_xml context 'no content responses' do let(:no_content_response) { ->(status) { [status, {}, ['']] } } - Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.each do |status| + STATUSES_WITHOUT_BODY = if Gem::Version.new(Rack.release) >= Gem::Version.new('2.1.0') + Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.keys + else + Rack::Utils::STATUS_WITH_NO_ENTITY_BODY + end + + STATUSES_WITHOUT_BODY.each do |status| it "does not modify a #{status} response" do expected_response = no_content_response[status] allow(app).to receive(:call).and_return(expected_response) From d61dc96038ce2a481cb1ec049d76e0555c9c4836 Mon Sep 17 00:00:00 2001 From: Nikolay Rys Date: Mon, 21 Oct 2019 18:07:39 +0200 Subject: [PATCH 124/290] Improve access to the controller context from middleware A documented helper method to access the context of the original controller from any Grape middleware. --- CHANGELOG.md | 1 + README.md | 15 +++++++++++++++ lib/grape.rb | 1 + lib/grape/middleware/auth/base.rb | 6 ++---- lib/grape/middleware/base.rb | 2 ++ lib/grape/middleware/helpers.rb | 10 ++++++++++ spec/grape/middleware/base_spec.rb | 8 ++++++++ 7 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 lib/grape/middleware/helpers.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ccc7aa9f..952903f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1918](https://github.com/ruby-grape/grape/pull/1918): Helper methods to access controller context from middleware - [@NikolayRys](https://github.com/NikolayRys). * [#1915](https://github.com/ruby-grape/grape/pull/1915): Micro optimizations in allocating hashes and arrays - [@dnesteryuk](https://github.com/dnesteryuk). * [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh). * [#1907](https://github.com/ruby-grape/grape/pull/1907): Adds outside configuration to Grape with `configure` - [@unleashy](https://github.com/unleashy). diff --git a/README.md b/README.md index ebaf1ac83..15801c344 100644 --- a/README.md +++ b/README.md @@ -2497,6 +2497,17 @@ class Twitter::API < Grape::API end ``` +Inside the `rescue_from` block, the environment of the original controller method(`.self` receiver) is accessible through the `#context` method. + +```ruby +class Twitter::API < Grape::API + rescue_from :all do |e| + user_id = context.params[:user_id] + error!("error for #{user_id}") + end +end +``` + #### Rescuing exceptions inside namespaces You could put `rescue_from` clauses inside a namespace and they will take precedence over ones @@ -3121,6 +3132,8 @@ end Use [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support. +You can access the controller params, headers, and helpers through the context with the `#context` method inside any auth middleware inherited from `Grape::Middlware::Auth::Base`. + ## Describing and Inspecting an API Grape routes can be reflected at runtime. This can notably be useful for generating documentation. @@ -3433,6 +3446,8 @@ class API < Grape::API end ``` +You can access the controller params, headers, and helpers through the context with the `#context` method inside any middleware inherited from `Grape::Middlware::Base`. + ### Rails Middleware Note that when you're using Grape mounted on Rails you don't have to use Rails middleware because it's already included into your middleware stack. diff --git a/lib/grape.rb b/lib/grape.rb index 97f0215b6..0708628e2 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -111,6 +111,7 @@ module Middleware autoload :Error autoload :Globals autoload :Stack + autoload :Helpers end module Auth diff --git a/lib/grape/middleware/auth/base.rb b/lib/grape/middleware/auth/base.rb index 1dd9bf66a..a3d34b131 100644 --- a/lib/grape/middleware/auth/base.rb +++ b/lib/grape/middleware/auth/base.rb @@ -4,6 +4,8 @@ module Grape module Middleware module Auth class Base + include Helpers + attr_accessor :options, :app, :env def initialize(app, **options) @@ -11,10 +13,6 @@ def initialize(app, **options) @options = options end - def context - env[Grape::Env::API_ENDPOINT] - end - def call(env) dup._call(env) end diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 08e26df57..945225a57 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -3,6 +3,8 @@ module Grape module Middleware class Base + include Helpers + attr_reader :app, :env, :options TEXT_HTML = 'text/html'.freeze diff --git a/lib/grape/middleware/helpers.rb b/lib/grape/middleware/helpers.rb new file mode 100644 index 000000000..eef648ed6 --- /dev/null +++ b/lib/grape/middleware/helpers.rb @@ -0,0 +1,10 @@ +module Grape + module Middleware + # Common methods for all types of Grape middleware + module Helpers + def context + env[Grape::Env::API_ENDPOINT] + end + end + end +end diff --git a/spec/grape/middleware/base_spec.rb b/spec/grape/middleware/base_spec.rb index 8163fd1f1..0e4a8de70 100644 --- a/spec/grape/middleware/base_spec.rb +++ b/spec/grape/middleware/base_spec.rb @@ -114,6 +114,14 @@ end end + describe '#context' do + subject { Grape::Middleware::Base.new(blank_app) } + it 'allows access to response context' do + subject.call(Grape::Env::API_ENDPOINT => { header: 'some header' }) + expect(subject.context).to eq(header: 'some header') + end + end + context 'options' do it 'persists options passed at initialization' do expect(Grape::Middleware::Base.new(blank_app, abc: true).options[:abc]).to be true From f0ed9c504edb7e700096b33ba13225cf8ec3688e Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Fri, 30 Aug 2019 16:09:46 +0200 Subject: [PATCH 125/290] Refactor mutual exclusion spec to use fake API --- .../validators/mutual_exclusion_spec.rb | 211 +++++++++++++++--- 1 file changed, 184 insertions(+), 27 deletions(-) diff --git a/spec/grape/validations/validators/mutual_exclusion_spec.rb b/spec/grape/validations/validators/mutual_exclusion_spec.rb index 9fa6375b5..0e8fbb06c 100644 --- a/spec/grape/validations/validators/mutual_exclusion_spec.rb +++ b/spec/grape/validations/validators/mutual_exclusion_spec.rb @@ -2,61 +2,218 @@ describe Grape::Validations::MutualExclusionValidator do describe '#validate!' do - let(:scope) do - Struct.new(:opts) do - def params(arg) - arg + subject(:validate) { post path, params } + + module ValidationsSpec + module MutualExclusionValidatorSpec + class API < Grape::API + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end + + params do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + post do + end + + params do + optional :beer + optional :wine + optional :grapefruit + optional :other + mutually_exclusive :beer, :wine, :grapefruit + end + post 'mixed-params' do + end + + params do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit, message: 'you should not mix beer and wine' + end + post '/custom-message' do + end + + params do + requires :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + end + post '/nested-hash' do + end + + params do + optional :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + end + post '/nested-optional-hash' do + end + + params do + requires :items, type: Array do + optional :beer + optional :wine + optional :grapefruit + mutually_exclusive :beer, :wine, :grapefruit + end + end + post '/nested-array' do + end + + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, :grapefruit, type: Boolean + mutually_exclusive :beer, :wine, :grapefruit + end + end + end + post '/deeply-nested-array' do + end end end end - let(:mutually_exclusive_params) { %i[beer wine grapefruit] } - let(:validator) { described_class.new(mutually_exclusive_params, {}, false, scope.new) } + + def app + ValidationsSpec::MutualExclusionValidatorSpec::API + end context 'when all mutually exclusive params are present' do + let(:path) { '/' } let(:params) { { beer: true, wine: true, grapefruit: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are mutually exclusive'] + ) end context 'mixed with other params' do - let(:mixed_params) { params.merge!(other: true, andanother: true) } + let(:path) { '/mixed-params' } + let(:params) { { beer: true, wine: true, grapefruit: true, other: true } } - it 'still raises a validation exception' do - expect do - validator.validate! mixed_params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are mutually exclusive'] + ) end end end context 'when a subset of mutually exclusive params are present' do + let(:path) { '/' } let(:params) { { beer: true, grapefruit: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,grapefruit' => ['are mutually exclusive'] + ) end end - context 'when params keys come as strings' do - let(:params) { { 'beer' => true, 'grapefruit' => true } } + context 'when custom message is specified' do + let(:path) { '/custom-message' } + let(:params) { { beer: true, wine: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine' => ['you should not mix beer and wine'] + ) end end context 'when no mutually exclusive params are present' do + let(:path) { '/' } let(:params) { { beer: true, somethingelse: true } } - it 'params' do - expect(validator.validate!(params)).to eql params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + + context 'when mutually exclusive params are nested inside required hash' do + let(:path) { '/nested-hash' } + let(:params) { { item: { beer: true, wine: true } } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'item[beer],item[wine]' => ['are mutually exclusive'] + ) + end + end + + context 'when mutually exclusive params are nested inside optional hash' do + let(:path) { '/nested-optional-hash' } + + context 'when params are passed' do + let(:params) { { item: { beer: true, wine: true } } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'item[beer],item[wine]' => ['are mutually exclusive'] + ) + end + end + + context 'when params are empty' do + let(:params) { {} } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + end + + context 'when mutually exclusive params are nested inside array' do + let(:path) { '/nested-array' } + let(:params) { { items: [{ beer: true, wine: true }, { wine: true, grapefruit: true }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[0][beer],items[0][wine]' => ['are mutually exclusive'], + 'items[1][wine],items[1][grapefruit]' => ['are mutually exclusive'] + ) + end + end + + context 'when mutually exclusive params are deeply nested' do + let(:path) { '/deeply-nested-array' } + let(:params) { { items: [{ nested_items: [{ beer: true, wine: true }] }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => ['are mutually exclusive'] + ) end end end From 4bfb1c5d214536d19c199cd4856d4dcc7a6a3308 Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Tue, 3 Sep 2019 16:57:54 +0200 Subject: [PATCH 126/290] Refactor at_least_one_of spec to use fake API --- .../validators/at_least_one_of_spec.rb | 210 ++++++++++++++---- 1 file changed, 172 insertions(+), 38 deletions(-) diff --git a/spec/grape/validations/validators/at_least_one_of_spec.rb b/spec/grape/validations/validators/at_least_one_of_spec.rb index 17eeb90a1..35044c899 100644 --- a/spec/grape/validations/validators/at_least_one_of_spec.rb +++ b/spec/grape/validations/validators/at_least_one_of_spec.rb @@ -2,75 +2,209 @@ describe Grape::Validations::AtLeastOneOfValidator do describe '#validate!' do - let(:scope) do - Struct.new(:opts) do - def params(arg) - arg - end - - def required?; end - - # mimics a method from Grape::Validations::ParamsScope which decides how a parameter must - # be named in errors - def full_name(name) - "food[#{name}]" + subject(:validate) { post path, params } + + module ValidationsSpec + module AtLeastOneOfValidatorSpec + class API < Grape::API + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end + + params do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit + end + post do + end + + params do + optional :beer, :wine, :grapefruit, :other + at_least_one_of :beer, :wine, :grapefruit + end + post 'mixed-params' do + end + + params do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'you should choose something' + end + post '/custom-message' do + end + + params do + requires :item, type: Hash do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'fail' + end + end + post '/nested-hash' do + end + + params do + requires :items, type: Array do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'fail' + end + end + post '/nested-array' do + end + + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, :grapefruit + at_least_one_of :beer, :wine, :grapefruit, message: 'fail' + end + end + end + post '/deeply-nested-array' do + end end end end - let(:at_least_one_of_params) { %i[beer wine grapefruit] } - let(:validator) { described_class.new(at_least_one_of_params, {}, false, scope.new) } + def app + ValidationsSpec::AtLeastOneOfValidatorSpec::API + end context 'when all restricted params are present' do + let(:path) { '/' } let(:params) { { beer: true, wine: true, grapefruit: true } } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 end context 'mixed with other params' do - let(:mixed_params) { params.merge!(other: true, andanother: true) } + let(:path) { '/mixed-params' } + let(:params) { { beer: true, wine: true, grapefruit: true, other: true } } - it 'does not raise a validation exception' do - expect(validator.validate!(mixed_params)).to eql mixed_params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 end end end context 'when a subset of restricted params are present' do + let(:path) { '/' } let(:params) { { beer: true, grapefruit: true } } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 end end - context 'when params keys come as strings' do - let(:params) { { 'beer' => true, 'grapefruit' => true } } + context 'when none of the restricted params is selected' do + let(:path) { '/' } + let(:params) { { other: true } } + + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are missing, at least one parameter must be provided'] + ) + end + + context 'when custom message is specified' do + let(:path) { '/custom-message' } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['you should choose something'] + ) + end end end - context 'when none of the restricted params is selected' do - let(:params) { { somethingelse: true } } - it 'raises a validation exception' do - expected_params = at_least_one_of_params.map { |p| "food[#{p}]" } - - expect { validator.validate! params }.to raise_error do |error| - expect(error).to be_a(Grape::Exceptions::Validation) - expect(error.params).to eq(expected_params) - expect(error.message).to eq(I18n.t('grape.errors.messages.at_least_one')) + context 'when exactly one of the restricted params is selected' do + let(:path) { '/' } + let(:params) { { beer: true } } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + + context 'when restricted params are nested inside hash' do + let(:path) { '/nested-hash' } + + context 'when at least one of them is present' do + let(:params) { { item: { beer: true, wine: true } } } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + + context 'when none of them are present' do + let(:params) { { item: { other: true } } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'item[beer],item[wine],item[grapefruit]' => ['fail'] + ) end end end - context 'when exactly one of the restricted params is selected' do - let(:params) { { beer: true, somethingelse: true } } + context 'when restricted params are nested inside array' do + let(:path) { '/nested-array' } + + context 'when at least one of them is present' do + let(:params) { { items: [{ beer: true, wine: true }, { grapefruit: true }] } } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + + context 'when none of them are present' do + let(:params) { { items: [{ beer: true, other: true }, { other: true }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[1][beer],items[1][wine],items[1][grapefruit]' => ['fail'] + ) + end + end + end + + context 'when restricted params are deeply nested' do + let(:path) { '/deeply-nested-array' } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + context 'when at least one of them is present' do + let(:params) { { items: [{ nested_items: [{ wine: true }] }] } } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + + context 'when none of them are present' do + let(:params) { { items: [{ nested_items: [{ other: true }] }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => ['fail'] + ) + end end end end From 96e4e1044e73e64414af709352b270ab4f88c864 Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Fri, 4 Oct 2019 17:42:06 +0200 Subject: [PATCH 127/290] Refactor exacly_one_of spec to use fake API --- .../validations/validators/exactly_one_of.rb | 25 +- .../validators/exactly_one_of_spec.rb | 240 +++++++++++++++--- 2 files changed, 206 insertions(+), 59 deletions(-) diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index 07283c783..dccdbb7d5 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -1,28 +1,11 @@ module Grape module Validations - require 'grape/validations/validators/mutual_exclusion' - class ExactlyOneOfValidator < MutualExclusionValidator - def validate!(params) - super - if scope_requires_params && none_of_restricted_params_is_present + require 'grape/validations/validators/multiple_params_base' + class ExactlyOneOfValidator < MultipleParamsBase + def validate_params!(params) + if keys_in_common(params).length != 1 raise Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one) end - params - end - - def message(default_key = nil) - options = instance_variable_get(:@option) - if options_key?(:message) - (options_key?(default_key, options[:message]) ? options[:message][default_key] : options[:message]) - else - default_key - end - end - - private - - def none_of_restricted_params_is_present - scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? } end end end diff --git a/spec/grape/validations/validators/exactly_one_of_spec.rb b/spec/grape/validations/validators/exactly_one_of_spec.rb index 8bdb23073..de06414dc 100644 --- a/spec/grape/validations/validators/exactly_one_of_spec.rb +++ b/spec/grape/validations/validators/exactly_one_of_spec.rb @@ -2,73 +2,237 @@ describe Grape::Validations::ExactlyOneOfValidator do describe '#validate!' do - let(:scope) do - Struct.new(:opts) do - def params(arg) - arg - end + subject(:validate) { post path, params } + + module ValidationsSpec + module ExactlyOneOfValidatorSpec + class API < Grape::API + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end + + params do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + post do + end + + params do + optional :beer + optional :wine + optional :grapefruit + optional :other + exactly_one_of :beer, :wine, :grapefruit + end + post 'mixed-params' do + end + + params do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit, message: 'you should choose one' + end + post '/custom-message' do + end - def required?; end + params do + requires :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + end + post '/nested-hash' do + end + + params do + optional :item, type: Hash do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + end + post '/nested-optional-hash' do + end + + params do + requires :items, type: Array do + optional :beer + optional :wine + optional :grapefruit + exactly_one_of :beer, :wine, :grapefruit + end + end + post '/nested-array' do + end + + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, :grapefruit, type: Boolean + exactly_one_of :beer, :wine, :grapefruit + end + end + end + post '/deeply-nested-array' do + end + end end end - let(:exactly_one_of_params) { %i[beer wine grapefruit] } - let(:validator) { described_class.new(exactly_one_of_params, {}, false, scope.new) } - context 'when all restricted params are present' do + def app + ValidationsSpec::ExactlyOneOfValidatorSpec::API + end + + context 'when all params are present' do + let(:path) { '/' } let(:params) { { beer: true, wine: true, grapefruit: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + ) end context 'mixed with other params' do - let(:mixed_params) { params.merge!(other: true, andanother: true) } + let(:path) { '/mixed-params' } + let(:params) { { beer: true, wine: true, grapefruit: true, other: true } } - it 'still raises a validation exception' do - expect do - validator.validate! mixed_params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + ) end end end - context 'when a subset of restricted params are present' do + context 'when a subset of params are present' do + let(:path) { '/' } let(:params) { { beer: true, grapefruit: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + ) end end - context 'when params keys come as strings' do - let(:params) { { 'beer' => true, 'grapefruit' => true } } + context 'when custom message is specified' do + let(:path) { '/custom-message' } + let(:params) { { beer: true, wine: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['you should choose one'] + ) end end - context 'when none of the restricted params is selected' do + context 'when exacly one param is present' do + let(:path) { '/' } + let(:params) { { beer: true, somethingelse: true } } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + + context 'when none of the params are present' do + let(:path) { '/' } let(:params) { { somethingelse: true } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + ) end end - context 'when exactly one of the restricted params is selected' do - let(:params) { { beer: true, somethingelse: true } } + context 'when params are nested inside required hash' do + let(:path) { '/nested-hash' } + let(:params) { { item: { beer: true, wine: true } } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided'] + ) + end + end + + context 'when params are nested inside optional hash' do + let(:path) { '/nested-optional-hash' } + + context 'when params are passed' do + let(:params) { { item: { beer: true, wine: true } } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided'] + ) + end + end + + context 'when params are empty' do + let(:params) { { other: true } } + + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 + end + end + end + + context 'when params are nested inside array' do + let(:path) { '/nested-array' } + let(:params) { { items: [{ beer: true, wine: true }, { wine: true, grapefruit: true }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[0][beer],items[0][wine],items[0][grapefruit]' => [ + 'are missing, exactly one parameter must be provided' + ], + 'items[1][beer],items[1][wine],items[1][grapefruit]' => [ + 'are missing, exactly one parameter must be provided' + ] + ) + end + end + + context 'when params are deeply nested' do + let(:path) { '/deeply-nested-array' } + let(:params) { { items: [{ nested_items: [{ beer: true, wine: true }] }] } } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => [ + 'are missing, exactly one parameter must be provided' + ] + ) end end end From 462b6a8b0e05b1c7993623d50eaa0a945fe0dcdb Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Fri, 4 Oct 2019 18:10:17 +0200 Subject: [PATCH 128/290] Refactor all_or_none spec to use fake API --- .../validators/all_or_none_spec.rb | 168 ++++++++++++++---- 1 file changed, 138 insertions(+), 30 deletions(-) diff --git a/spec/grape/validations/validators/all_or_none_spec.rb b/spec/grape/validations/validators/all_or_none_spec.rb index 98d82ce8f..31abc57cf 100644 --- a/spec/grape/validations/validators/all_or_none_spec.rb +++ b/spec/grape/validations/validators/all_or_none_spec.rb @@ -2,58 +2,166 @@ describe Grape::Validations::AllOrNoneOfValidator do describe '#validate!' do - let(:scope) do - Struct.new(:opts) do - def params(arg) - arg - end + subject(:validate) { post path, params } + + module ValidationsSpec + module AllOrNoneOfValidatorSpec + class API < Grape::API + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!(e.errors.transform_keys! { |key| key.join(',') }, 400) + end + + params do + optional :beer, :wine, type: Boolean + all_or_none_of :beer, :wine + end + post do + end + + params do + optional :beer, :wine, :other, type: Boolean + all_or_none_of :beer, :wine + end + post 'mixed-params' do + end + + params do + optional :beer, :wine, type: Boolean + all_or_none_of :beer, :wine, message: 'choose all or none' + end + post '/custom-message' do + end + + params do + requires :item, type: Hash do + optional :beer, :wine, type: Boolean + all_or_none_of :beer, :wine + end + end + post '/nested-hash' do + end + + params do + requires :items, type: Array do + optional :beer, :wine, type: Boolean + all_or_none_of :beer, :wine + end + end + post '/nested-array' do + end - def required?; end + params do + requires :items, type: Array do + requires :nested_items, type: Array do + optional :beer, :wine, type: Boolean + all_or_none_of :beer, :wine + end + end + end + post '/deeply-nested-array' do + end + end end end - let(:all_or_none_params) { %i[beer wine grapefruit] } - let(:validator) { described_class.new(all_or_none_params, {}, false, scope.new) } + + def app + ValidationsSpec::AllOrNoneOfValidatorSpec::API + end context 'when all restricted params are present' do - let(:params) { { beer: true, wine: true, grapefruit: true } } + let(:path) { '/' } + let(:params) { { beer: true, wine: true } } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 end context 'mixed with other params' do - let(:mixed_params) { params.merge!(other: true, andanother: true) } + let(:path) { '/mixed-params' } + let(:params) { { beer: true, wine: true, other: true } } - it 'does not raise a validation exception' do - expect(validator.validate!(mixed_params)).to eql mixed_params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 end end end - context 'when none of the restricted params is selected' do + context 'when a subset of restricted params are present' do + let(:path) { '/' } + let(:params) { { beer: true } } + + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine' => ['provide all or none of parameters'] + ) + end + end + + context 'when custom message is specified' do + let(:path) { '/custom-message' } + let(:params) { { beer: true } } + + it 'returns a validation error' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'beer,wine' => ['choose all or none'] + ) + end + end + + context 'when no restricted params are present' do + let(:path) { '/' } let(:params) { { somethingelse: true } } - it 'does not raise a validation exception' do - expect(validator.validate!(params)).to eql params + it 'does not return a validation error' do + validate + expect(last_response.status).to eq 201 end end - context 'when only a subset of restricted params are present' do - let(:params) { { beer: true, grapefruit: true } } + context 'when restricted params are nested inside required hash' do + let(:path) { '/nested-hash' } + let(:params) { { item: { beer: true } } } - it 'raises a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'item[beer],item[wine]' => ['provide all or none of parameters'] + ) end - context 'mixed with other params' do - let(:mixed_params) { params.merge!(other: true, andanother: true) } + end - it 'raise a validation exception' do - expect do - validator.validate! params - end.to raise_error(Grape::Exceptions::Validation) - end + context 'when mutually exclusive params are nested inside array' do + let(:path) { '/nested-array' } + let(:params) { { items: [{ beer: true, wine: true }, { wine: true }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[1][beer],items[1][wine]' => ['provide all or none of parameters'] + ) + end + end + + context 'when mutually exclusive params are deeply nested' do + let(:path) { '/deeply-nested-array' } + let(:params) { { items: [{ nested_items: [{ beer: true }] }] } } + + it 'returns a validation error with full names of the params' do + validate + expect(last_response.status).to eq 400 + expect(JSON.parse(last_response.body)).to eq( + 'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [ + 'provide all or none of parameters' + ] + ) end end end From dec377a61125a1cccc58e9a83c0bc87449b21720 Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Tue, 3 Sep 2019 17:12:45 +0200 Subject: [PATCH 129/290] Refactor unless condition in validate! method in base validator --- lib/grape/validations/validators/base.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index c62956acc..673db01c3 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -36,17 +36,18 @@ def validate(request) # @return [void] def validate!(params) attributes = AttributesIterator.new(self, @scope, params) + # we collect errors inside array because + # there may be more than one error per field array_errors = [] + attributes.each do |resource_params, attr_name| next if !@scope.required? && resource_params.empty? - next unless @required || (resource_params.respond_to?(:key?) && resource_params.key?(attr_name)) next unless @scope.meets_dependency?(resource_params, params) - begin - validate_param!(attr_name, resource_params) + if @required || resource_params.respond_to?(:key?) && resource_params.key?(attr_name) + validate_param!(attr_name, resource_params) + end rescue Grape::Exceptions::Validation => e - # we collect errors inside array because - # there may be more than one error per field array_errors << e end end From ab1a256ff4217fa3b3f5976fa44e3d505aad175d Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Fri, 4 Oct 2019 17:58:43 +0200 Subject: [PATCH 130/290] Fix multiple params validators to return correct messages Previously all the multiple params validators returned messages without considering the scope. For example, in case of hash params: `{ "param_1,param_2": 'error message' }` instead of: `{ "hash[param_1],hash[param_2]": 'error message' }` Or in case of arrays indexes were ignored completely: `{ "param_1,param_2": 'error message' }` instead of: `{ "array[1][param_1],array[1][param_2]": 'error message' }` This commit fixes it by using AttributesIterator in a similar way to Grape::Validations::Base. --- CHANGELOG.md | 1 + lib/grape/validations/attributes_iterator.rb | 11 +++++--- .../validations/validators/all_or_none.rb | 19 +++++--------- .../validations/validators/at_least_one_of.rb | 17 +++--------- .../validations/validators/exactly_one_of.rb | 8 +++--- .../validators/multiple_params_base.rb | 26 ++++++++++++------- .../validators/mutual_exclusion.rb | 24 +++++------------ 7 files changed, 44 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50dbcd0eb..504f4227c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * [#1898](https://github.com/ruby-grape/grape/pull/1898): Refactor `ValidatorFactory` to improve memory allocation - [@Bhacaz](https://github.com/Bhacaz). * [#1900](https://github.com/ruby-grape/grape/pull/1900): Define boolean for `Grape::Api::Instance` - [@Bhacaz](https://github.com/Bhacaz). * [#1903](https://github.com/ruby-grape/grape/pull/1903): Allow nested params renaming (Hash/Array) - [@bikolya](https://github.com/bikolya). +* [#1913](https://github.com/ruby-grape/grape/pull/1913): Fix multiple params validators to return correct messages for nested params - [@bikolya](https://github.com/bikolya). ### 1.2.4 (2019/06/13) diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index 18b40cce7..5aad071f0 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -5,11 +5,12 @@ class AttributesIterator attr_reader :scope - def initialize(validator, scope, params) + def initialize(validator, scope, params, multiple_params: false) @scope = scope @attrs = validator.attrs @original_params = scope.params(params) @params = Array.wrap(@original_params) + @multiple_params = multiple_params end def each(&block) @@ -41,8 +42,12 @@ def do_each(params_to_process, parent_indicies = [], &block) @scope.index = index end - @attrs.each do |attr_name| - yield resource_params, attr_name, inside_array + if @multiple_params + yield resource_params, @attrs + else + @attrs.each do |attr_name| + yield resource_params, attr_name + end end end end diff --git a/lib/grape/validations/validators/all_or_none.rb b/lib/grape/validations/validators/all_or_none.rb index 02a06851d..5062044c9 100644 --- a/lib/grape/validations/validators/all_or_none.rb +++ b/lib/grape/validations/validators/all_or_none.rb @@ -1,19 +1,12 @@ +require 'grape/validations/validators/multiple_params_base' + module Grape module Validations - require 'grape/validations/validators/multiple_params_base' class AllOrNoneOfValidator < MultipleParamsBase - def validate!(params) - super - if scope_requires_params && only_subset_present - raise Grape::Exceptions::Validation, params: all_keys, message: message(:all_or_none) - end - params - end - - private - - def only_subset_present - scoped_params.any? { |resource_params| !keys_in_common(resource_params).empty? && keys_in_common(resource_params).length < attrs.length } + def validate_params!(params) + keys = keys_in_common(params) + return if keys.empty? || keys.length == all_keys.length + raise Grape::Exceptions::Validation, params: all_keys, message: message(:all_or_none) end end end diff --git a/lib/grape/validations/validators/at_least_one_of.rb b/lib/grape/validations/validators/at_least_one_of.rb index cfe006c89..5859d41d4 100644 --- a/lib/grape/validations/validators/at_least_one_of.rb +++ b/lib/grape/validations/validators/at_least_one_of.rb @@ -3,20 +3,9 @@ module Grape module Validations class AtLeastOneOfValidator < MultipleParamsBase - def validate!(params) - super - if scope_requires_params && no_exclusive_params_are_present - scoped_params = all_keys.map { |key| @scope.full_name(key) } - raise Grape::Exceptions::Validation, params: scoped_params, - message: message(:at_least_one) - end - params - end - - private - - def no_exclusive_params_are_present - scoped_params.any? { |resource_params| keys_in_common(resource_params).empty? } + def validate_params!(params) + return unless keys_in_common(params).empty? + raise Grape::Exceptions::Validation, params: all_keys, message: message(:at_least_one) end end end diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index dccdbb7d5..36ca3b10d 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -1,11 +1,11 @@ +require 'grape/validations/validators/multiple_params_base' + module Grape module Validations - require 'grape/validations/validators/multiple_params_base' class ExactlyOneOfValidator < MultipleParamsBase def validate_params!(params) - if keys_in_common(params).length != 1 - raise Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one) - end + return if keys_in_common(params).length == 1 + raise Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one) end end end diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index a8419661b..b733c65dd 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -1,26 +1,32 @@ module Grape module Validations class MultipleParamsBase < Base - attr_reader :scoped_params - + # rubocop:disable HashEachMethods def validate!(params) - @scoped_params = [@scope.params(params)].flatten - params - end + attributes = AttributesIterator.new(self, @scope, params, multiple_params: true) + array_errors = [] - private + attributes.each do |resource_params, _| + begin + validate_params!(resource_params) + rescue Grape::Exceptions::Validation => e + array_errors << e + end + end - def scope_requires_params - @scope.required? || scoped_params.any? { |param| param.respond_to?(:any?) && param.any? } + raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any? end + # rubocop:enable HashEachMethods + + private def keys_in_common(resource_params) return [] unless resource_params.is_a?(Hash) - (all_keys & resource_params.stringify_keys.keys).map(&:to_s) + all_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) } end def all_keys - attrs.map(&:to_s) + attrs.map { |attr| @scope.full_name(attr) } end end end diff --git a/lib/grape/validations/validators/mutual_exclusion.rb b/lib/grape/validations/validators/mutual_exclusion.rb index d46d56351..efe849f7c 100644 --- a/lib/grape/validations/validators/mutual_exclusion.rb +++ b/lib/grape/validations/validators/mutual_exclusion.rb @@ -1,24 +1,12 @@ +require 'grape/validations/validators/multiple_params_base' + module Grape module Validations - require 'grape/validations/validators/multiple_params_base' class MutualExclusionValidator < MultipleParamsBase - attr_reader :processing_keys_in_common - - def validate!(params) - super - if two_or_more_exclusive_params_are_present - raise Grape::Exceptions::Validation, params: processing_keys_in_common, message: message(:mutual_exclusion) - end - params - end - - private - - def two_or_more_exclusive_params_are_present - scoped_params.any? do |resource_params| - @processing_keys_in_common = keys_in_common(resource_params) - @processing_keys_in_common.length > 1 - end + def validate_params!(params) + keys = keys_in_common(params) + return if keys.length <= 1 + raise Grape::Exceptions::Validation, params: keys, message: message(:mutual_exclusion) end end end From 31224dd2078d04ef38fba17305d459be94ed5d69 Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Fri, 4 Oct 2019 18:47:39 +0200 Subject: [PATCH 131/290] Fix old validation specs for multiple params TBH, no idea how they managed to pass before --- spec/grape/exceptions/validation_errors_spec.rb | 10 ++++++---- spec/grape/validations_spec.rb | 16 +++++++++------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/spec/grape/exceptions/validation_errors_spec.rb b/spec/grape/exceptions/validation_errors_spec.rb index cbf1a1853..174350e6e 100644 --- a/spec/grape/exceptions/validation_errors_spec.rb +++ b/spec/grape/exceptions/validation_errors_spec.rb @@ -77,10 +77,12 @@ def app end get '/exactly_one_of', beer: 'string', wine: 'anotherstring' expect(last_response.status).to eq(400) - expect(JSON.parse(last_response.body)).to eq([ - 'params' => %w[beer wine], - 'messages' => ['are mutually exclusive'] - ]) + expect(JSON.parse(last_response.body)).to eq( + [ + 'params' => %w[beer wine juice], + 'messages' => ['are missing, exactly one parameter must be provided'] + ] + ) end end end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index c8bd714d6..7650828fe 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -1233,7 +1233,9 @@ def validate_param!(attr_name, params) end get '/custom_message/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }] expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer, wine are mutually exclusive pass only one, scotch, aquavit are mutually exclusive pass only one, scotch2, aquavit2 are mutually exclusive pass only one' + expect(last_response.body).to eq( + 'beer, wine are mutually exclusive pass only one, nested[scotch], nested[aquavit] are mutually exclusive pass only one, nested2[1][scotch2], nested2[1][aquavit2] are mutually exclusive pass only one' + ) end end @@ -1259,7 +1261,7 @@ def validate_param!(attr_name, params) get '/mutually_exclusive', beer: 'true', wine: 'true', nested: { scotch: 'true', aquavit: 'true' }, nested2: [{ scotch2: 'true' }, { scotch2: 'true', aquavit2: 'true' }] expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer, wine are mutually exclusive, scotch, aquavit are mutually exclusive, scotch2, aquavit2 are mutually exclusive' + expect(last_response.body).to eq 'beer, wine are mutually exclusive, nested[scotch], nested[aquavit] are mutually exclusive, nested2[1][scotch2], nested2[1][aquavit2] are mutually exclusive' end end @@ -1328,7 +1330,7 @@ def validate_param!(attr_name, params) optional :beer optional :wine optional :juice - exactly_one_of :beer, :wine, :juice, message: { exactly_one: 'are missing, exactly one parameter is required', mutual_exclusion: 'are mutually exclusive, exactly one parameter is required' } + exactly_one_of :beer, :wine, :juice, message: 'are missing, exactly one parameter is required' end get '/exactly_one_of' do 'exactly_one_of works!' @@ -1362,7 +1364,7 @@ def validate_param!(attr_name, params) it 'errors when two or more are present' do get '/custom_message/exactly_one_of', beer: 'string', wine: 'anotherstring' expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer, wine are mutually exclusive, exactly one parameter is required' + expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter is required' end end @@ -1381,7 +1383,7 @@ def validate_param!(attr_name, params) it 'errors when two or more are present' do get '/exactly_one_of', beer: 'string', wine: 'anotherstring' expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer, wine are mutually exclusive' + expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided' end end @@ -1409,7 +1411,7 @@ def validate_param!(attr_name, params) it 'errors when none are present' do get '/exactly_one_of_nested' expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'nested is missing, beer_nested, wine_nested, juice_nested are missing, exactly one parameter must be provided' + expect(last_response.body).to eq 'nested is missing, nested[beer_nested], nested[wine_nested], nested[juice_nested] are missing, exactly one parameter must be provided' end it 'succeeds when one is present' do @@ -1421,7 +1423,7 @@ def validate_param!(attr_name, params) it 'errors when two or more are present' do get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }] expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer_nested2, wine_nested2 are mutually exclusive' + expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2], nested2[0][juice_nested2] are missing, exactly one parameter must be provided' end end end From b9c1a3279fd18ea57d402ab9ea5fb3b527be3839 Mon Sep 17 00:00:00 2001 From: Nick Bienko Date: Thu, 24 Oct 2019 10:36:44 +0200 Subject: [PATCH 132/290] Split attributes iterator into single/multiple attributes iterators --- lib/grape.rb | 2 ++ lib/grape/validations/attributes_iterator.rb | 15 ++++----- .../multiple_attributes_iterator.rb | 11 +++++++ .../validations/single_attribute_iterator.rb | 13 ++++++++ lib/grape/validations/validators/base.rb | 2 +- lib/grape/validations/validators/default.rb | 2 +- .../validators/multiple_params_base.rb | 6 ++-- .../multiple_attributes_iterator_spec.rb | 29 ++++++++++++++++ .../single_attribute_iterator_spec.rb | 33 +++++++++++++++++++ 9 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 lib/grape/validations/multiple_attributes_iterator.rb create mode 100644 lib/grape/validations/single_attribute_iterator.rb create mode 100644 spec/grape/validations/multiple_attributes_iterator_spec.rb create mode 100644 spec/grape/validations/single_attribute_iterator_spec.rb diff --git a/lib/grape.rb b/lib/grape.rb index 0708628e2..79818a1ab 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -225,6 +225,8 @@ module ServeFile require 'grape/validations/validators/base' require 'grape/validations/attributes_iterator' +require 'grape/validations/single_attribute_iterator' +require 'grape/validations/multiple_attributes_iterator' require 'grape/validations/validators/allow_blank' require 'grape/validations/validators/as' require 'grape/validations/validators/at_least_one_of' diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index 5aad071f0..5a9609b70 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -5,12 +5,11 @@ class AttributesIterator attr_reader :scope - def initialize(validator, scope, params, multiple_params: false) + def initialize(validator, scope, params) @scope = scope @attrs = validator.attrs @original_params = scope.params(params) @params = Array.wrap(@original_params) - @multiple_params = multiple_params end def each(&block) @@ -42,15 +41,13 @@ def do_each(params_to_process, parent_indicies = [], &block) @scope.index = index end - if @multiple_params - yield resource_params, @attrs - else - @attrs.each do |attr_name| - yield resource_params, attr_name - end - end + yield_attributes(resource_params, @attrs, &block) end end + + def yield_attributes(_resource_params, _attrs) + raise NotImplementedError + end end end end diff --git a/lib/grape/validations/multiple_attributes_iterator.rb b/lib/grape/validations/multiple_attributes_iterator.rb new file mode 100644 index 000000000..95c6b27c9 --- /dev/null +++ b/lib/grape/validations/multiple_attributes_iterator.rb @@ -0,0 +1,11 @@ +module Grape + module Validations + class MultipleAttributesIterator < AttributesIterator + private + + def yield_attributes(resource_params, _attrs) + yield resource_params + end + end + end +end diff --git a/lib/grape/validations/single_attribute_iterator.rb b/lib/grape/validations/single_attribute_iterator.rb new file mode 100644 index 000000000..1ad06fe71 --- /dev/null +++ b/lib/grape/validations/single_attribute_iterator.rb @@ -0,0 +1,13 @@ +module Grape + module Validations + class SingleAttributeIterator < AttributesIterator + private + + def yield_attributes(resource_params, attrs) + attrs.each do |attr_name| + yield resource_params, attr_name + end + end + end + end +end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 673db01c3..0594116eb 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -35,7 +35,7 @@ def validate(request) # @raise [Grape::Exceptions::Validation] if validation failed # @return [void] def validate!(params) - attributes = AttributesIterator.new(self, @scope, params) + attributes = SingleAttributeIterator.new(self, @scope, params) # we collect errors inside array because # there may be more than one error per field array_errors = [] diff --git a/lib/grape/validations/validators/default.rb b/lib/grape/validations/validators/default.rb index c472cf31b..102458f14 100644 --- a/lib/grape/validations/validators/default.rb +++ b/lib/grape/validations/validators/default.rb @@ -18,7 +18,7 @@ def validate_param!(attr_name, params) end def validate!(params) - attrs = AttributesIterator.new(self, @scope, params) + attrs = SingleAttributeIterator.new(self, @scope, params) attrs.each do |resource_params, attr_name| if resource_params.is_a?(Hash) && resource_params[attr_name].nil? validate_param!(attr_name, resource_params) diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index b733c65dd..023e2f5c9 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -1,12 +1,11 @@ module Grape module Validations class MultipleParamsBase < Base - # rubocop:disable HashEachMethods def validate!(params) - attributes = AttributesIterator.new(self, @scope, params, multiple_params: true) + attributes = MultipleAttributesIterator.new(self, @scope, params) array_errors = [] - attributes.each do |resource_params, _| + attributes.each do |resource_params| begin validate_params!(resource_params) rescue Grape::Exceptions::Validation => e @@ -16,7 +15,6 @@ def validate!(params) raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any? end - # rubocop:enable HashEachMethods private diff --git a/spec/grape/validations/multiple_attributes_iterator_spec.rb b/spec/grape/validations/multiple_attributes_iterator_spec.rb new file mode 100644 index 000000000..ef7ead6f6 --- /dev/null +++ b/spec/grape/validations/multiple_attributes_iterator_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Grape::Validations::MultipleAttributesIterator do + describe '#each' do + subject(:iterator) { described_class.new(validator, scope, params) } + let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) } + let(:validator) { double(attrs: %i[first second third]) } + + context 'when params is a hash' do + let(:params) do + { first: 'string', second: 'string' } + end + + it 'yields the whole params hash without the list of attrs' do + expect { |b| iterator.each(&b) }.to yield_with_args(params) + end + end + + context 'when params is an array' do + let(:params) do + [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }] + end + + it 'yields each element of the array without the list of attrs' do + expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1]) + end + end + end +end diff --git a/spec/grape/validations/single_attribute_iterator_spec.rb b/spec/grape/validations/single_attribute_iterator_spec.rb new file mode 100644 index 000000000..ab884f702 --- /dev/null +++ b/spec/grape/validations/single_attribute_iterator_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe Grape::Validations::SingleAttributeIterator do + describe '#each' do + subject(:iterator) { described_class.new(validator, scope, params) } + let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) } + let(:validator) { double(attrs: %i[first second third]) } + + context 'when params is a hash' do + let(:params) do + { first: 'string', second: 'string' } + end + + it 'yields params and every single attribute from the list' do + expect { |b| iterator.each(&b) } + .to yield_successive_args([params, :first], [params, :second], [params, :third]) + end + end + + context 'when params is an array' do + let(:params) do + [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }] + end + + it 'yields every single attribute from the list for each of the array elements' do + expect { |b| iterator.each(&b) }.to yield_successive_args( + [params[0], :first], [params[0], :second], [params[0], :third], + [params[1], :first], [params[1], :second], [params[1], :third] + ) + end + end + end +end From 4b0100fdff238f2860eac767119681f47be9665a Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sun, 27 Oct 2019 21:10:51 +0200 Subject: [PATCH 133/290] Minor readme fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15801c344..1fa73bbd7 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ module Twitter desc 'Return a status.' params do - requires :id, type: Integer, desc: 'Status id.' + requires :id, type: Integer, desc: 'Status ID.' end route_param :id do get do From d7d2674dff89caeddaf5ddf4c282c6bac72eaebe Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Wed, 30 Oct 2019 17:31:35 +0000 Subject: [PATCH 134/290] [Issue-1908] Addresses Issue 1908 (#1926) --- CHANGELOG.md | 1 + lib/grape/dsl/desc.rb | 13 +++++++++-- spec/grape/api_remount_spec.rb | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 504f4227c..9c0979e76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * [#1900](https://github.com/ruby-grape/grape/pull/1900): Define boolean for `Grape::Api::Instance` - [@Bhacaz](https://github.com/Bhacaz). * [#1903](https://github.com/ruby-grape/grape/pull/1903): Allow nested params renaming (Hash/Array) - [@bikolya](https://github.com/bikolya). * [#1913](https://github.com/ruby-grape/grape/pull/1913): Fix multiple params validators to return correct messages for nested params - [@bikolya](https://github.com/bikolya). +* [#1926](https://github.com/ruby-grape/grape/pull/1926): Fixes configuration within given or mounted blocks - [@myxoh](https://github.com/myxoh). ### 1.2.4 (2019/06/13) diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index 865da1ed7..e8122b544 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -49,8 +49,17 @@ module Desc # def desc(description, options = {}, &config_block) if block_given? - configuration = defined?(configuration) && configuration.respond_to?(:evaluate) ? configuration.evaluate : {} - config_class = desc_container(configuration) + endpoint_configuration = if defined?(configuration) + # When the instance is mounted - the configuration is executed on mount time + if configuration.respond_to?(:evaluate) + configuration.evaluate + # Within `given` or `mounted blocks` the configuration is already evaluated + elsif configuration.is_a?(Hash) + configuration + end + end + endpoint_configuration ||= {} + config_class = desc_container(endpoint_configuration) config_class.configure do description description diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index 3e95eff3f..ea4ae3b3e 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -97,6 +97,48 @@ def app end end + context 'when executing a standard block within a `mounted` block with all dynamic params' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + mounted do + desc configuration[:description] do + headers configuration[:headers] + end + get configuration[:endpoint] do + configuration[:response] + end + end + end + end + + let(:api_endpoint) { 'custom_endpoint' } + let(:api_response) { 'custom response' } + let(:endpoint_description) { 'this is a custom API' } + let(:headers) do + { + 'XAuthToken' => { + 'description' => 'Validates your identity', + 'required' => true + } + } + end + + it 'mounts the API and obtains the description and headers definition' do + root_api.mount a_remounted_api, with: { + description: endpoint_description, + headers: headers, + endpoint: api_endpoint, + response: api_response + } + get api_endpoint + expect(last_response.body).to eq api_response + expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:description]) + .to eq endpoint_description + expect(a_remounted_api.instances.last.endpoints.first.options[:route_options][:headers]) + .to eq headers + end + end + context 'when executing a custom block on mount' do subject(:a_remounted_api) do Class.new(Grape::API) do From 3f9bcf90f28d79f353c1f984b332cefe1a171711 Mon Sep 17 00:00:00 2001 From: Timo Recke Date: Tue, 5 Nov 2019 13:25:44 +0100 Subject: [PATCH 135/290] Expand route documentation in the README (#1923) This adds some more documentation, that was partly implicitly covered by the Basic Usage example but partly was only to be got by the method documentation. As this is an essential part of the DSL this it added for the new users convenience. --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/README.md b/README.md index 1fa73bbd7..0f28b89a4 100644 --- a/README.md +++ b/README.md @@ -1916,6 +1916,56 @@ error! 'Unauthorized', 401, 'X-Error-Detail' => 'Invalid token.' ## Routes +To define routes you can use the `route` method or the shorthands for the HTTP verbs. To define a route that accepts any route set to `:any`. +Parts of the path that are denoted with a colon will be interpreted as route parameters. + +```ruby +route :get, 'status' do +end + +# is the same as + +get 'status' do +end + +# is the same as + +get :status do +end + +# is NOT the same as + +get ':status' do # this makes param[:status] available +end + +# This will make both param[:status_id] and param[:id] available + +get 'statuses/:status_id/reviews/:id' do +end +``` + +To declare a namespace that prefixes all routes within, use the `namespace` method. `group`, `resource`, `resources` and `segment` are aliases to this method. Any endpoints within will share their parent context as well as any configuration done in the namespace context. + +The `route_param` method is a convenient method for defining a parameter route segment. If you define a type, it will add a validation for this parameter. + +```ruby +route_param :id, type: Integer do + get 'status' do + end +end + +# is the same as + +namespace ':id' do + params do + requires :id, type: Integer + end + + get 'status' do + end +end +``` + Optionally, you can define requirements for your named route parameters using regular expressions on namespace or endpoint. The route will match only if all requirements are met. From c3de1846e028115e85098ac055a3b9f9efd5dc1d Mon Sep 17 00:00:00 2001 From: Nicolas Klein Date: Thu, 14 Nov 2019 18:02:46 +0000 Subject: [PATCH 136/290] [LazyBlock] Allows using complex expressions with configuration (#1931) * Introduces the concept of a LazyBlock which gets excecuted on mount * Fixes LazyBlock within a namespace * Adds more specs so that given can also be used for conditional lazy blocks * Documents expressions * Adds a changelog * Update README.md --- CHANGELOG.md | 1 + README.md | 36 ++++++++++ lib/grape.rb | 1 + lib/grape/api.rb | 10 ++- lib/grape/api/instance.rb | 26 +++++--- lib/grape/util/lazy_block.rb | 25 +++++++ lib/grape/util/lazy_value.rb | 5 ++ spec/grape/api_remount_spec.rb | 116 +++++++++++++++++++++++++++++++++ 8 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 lib/grape/util/lazy_block.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0979e76..c8b1977fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1931](https://github.com/ruby-grape/grape/pull/1931): Introduces LazyBlock to generate expressions that will executed at mount time - [@myxoh](https://github.com/myxoh). * [#1918](https://github.com/ruby-grape/grape/pull/1918): Helper methods to access controller context from middleware - [@NikolayRys](https://github.com/NikolayRys). * [#1915](https://github.com/ruby-grape/grape/pull/1915): Micro optimizations in allocating hashes and arrays - [@dnesteryuk](https://github.com/dnesteryuk). * [#1904](https://github.com/ruby-grape/grape/pull/1904): Allows Grape to load files on startup rather than on the first call - [@myxoh](https://github.com/myxoh). diff --git a/README.md b/README.md index 0f28b89a4..c5a9a46a2 100644 --- a/README.md +++ b/README.md @@ -485,6 +485,42 @@ class ConditionalEndpoint::API < Grape::API end ``` +More complex results can be achieved by using `mounted` as an expression within which the `configuration` is already evaluated as a Hash. + +```ruby +class ExpressionEndpointAPI < Grape::API + get(mounted { configuration[:route_name] || 'default_name' }) do + # some logic + end +end +``` + +```ruby +class BasicAPI < Grape::API + desc 'Statuses index' do + params: mounted { configuration[:entity] || API::Entities::Status }.documentation + end + params do + requires :all, using: mounted { configuration[:entity] || API::Entities::Status }.documentation + end + get '/statuses' do + statuses = Status.all + type = current_user.admin? ? :full : :default + present statuses, with: mounted { configuration[:entity] || API::Entities::Status }, type: type + end +end + +class V1 < Grape::API + version 'v1' + mount BasicAPI, with: { entity: mounted { configuration[:entity] || API::Enitities::Status } } +end + +class V2 < Grape::API + version 'v2' + mount BasicAPI, with: { entity: mounted { configuration[:entity] || API::Enitities::V2::Status } } +end +``` + ## Versioning There are four strategies in which clients can reach your API's endpoints: `:path`, diff --git a/lib/grape.rb b/lib/grape.rb index 79818a1ab..e211e046e 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -221,6 +221,7 @@ module ServeFile require 'grape/config' require 'grape/util/content_types' require 'grape/util/lazy_value' +require 'grape/util/lazy_block' require 'grape/util/endpoint_configuration' require 'grape/validations/validators/base' diff --git a/lib/grape/api.rb b/lib/grape/api.rb index fa707d2e9..ac9a7513a 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -149,7 +149,13 @@ def add_setup(method, *args, &block) def replay_step_on(instance, setup_step) return if skip_immediate_run?(instance, setup_step[:args]) - instance.send(setup_step[:method], *evaluate_arguments(instance.configuration, *setup_step[:args]), &setup_step[:block]) + args = evaluate_arguments(instance.configuration, *setup_step[:args]) + response = instance.send(setup_step[:method], *args, &setup_step[:block]) + if skip_immediate_run?(instance, [response]) + response + else + evaluate_arguments(instance.configuration, response).first + end end # Skips steps that contain arguments to be lazily executed (on re-mount time) @@ -165,7 +171,7 @@ def any_lazy?(args) def evaluate_arguments(configuration, *args) args.map do |argument| if argument.respond_to?(:lazy?) && argument.lazy? - configuration.fetch(argument.access_keys).evaluate + argument.evaluate_from(configuration) elsif argument.is_a?(Hash) argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h elsif argument.is_a?(Array) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 0d3d43d21..5d8ac7cd4 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -13,12 +13,11 @@ class << self attr_accessor :configuration def given(conditional_option, &block) - evaluate_as_instance_with_configuration(block) if conditional_option && block_given? + evaluate_as_instance_with_configuration(block, lazy: true) if conditional_option && block_given? end def mounted(&block) - return if base_instance? - evaluate_as_instance_with_configuration(block) + evaluate_as_instance_with_configuration(block, lazy: true) end def base=(grape_api) @@ -110,14 +109,21 @@ def nest(*blocks, &block) end end - def evaluate_as_instance_with_configuration(block) - value_for_configuration = configuration - if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy? - self.configuration = value_for_configuration.evaluate + def evaluate_as_instance_with_configuration(block, lazy: false) + lazy_block = Grape::Util::LazyBlock.new do |configuration| + value_for_configuration = configuration + if value_for_configuration.respond_to?(:lazy?) && value_for_configuration.lazy? + self.configuration = value_for_configuration.evaluate + end + response = instance_eval(&block) + self.configuration = value_for_configuration + response + end + if base_instance? && lazy + lazy_block + else + lazy_block.evaluate_from(configuration) end - response = instance_eval(&block) - self.configuration = value_for_configuration - response end def inherited(subclass) diff --git a/lib/grape/util/lazy_block.rb b/lib/grape/util/lazy_block.rb new file mode 100644 index 000000000..7fe842e19 --- /dev/null +++ b/lib/grape/util/lazy_block.rb @@ -0,0 +1,25 @@ +module Grape + module Util + class LazyBlock + def initialize(&new_block) + @block = new_block + end + + def evaluate_from(configuration) + @block.call(configuration) + end + + def evaluate + @block.call({}) + end + + def lazy? + true + end + + def to_s + evaluate.to_s + end + end + end +end diff --git a/lib/grape/util/lazy_value.rb b/lib/grape/util/lazy_value.rb index 91add6508..0d01c66f0 100644 --- a/lib/grape/util/lazy_value.rb +++ b/lib/grape/util/lazy_value.rb @@ -7,6 +7,11 @@ def initialize(value, access_keys = []) @access_keys = access_keys end + def evaluate_from(configuration) + matching_lazy_value = configuration.fetch(@access_keys) + matching_lazy_value.evaluate + end + def evaluate @value end diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index ea4ae3b3e..f0f60a2a5 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -97,6 +97,54 @@ def app end end + context 'when using an expression derived from a configuration' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + get(mounted { "api_name_#{configuration[:api_name]}" }) do + 'success' + end + end + end + + before do + root_api.mount a_remounted_api, with: { + api_name: 'a_name' + } + end + + it 'mounts the endpoint with the name' do + get 'api_name_a_name' + expect(last_response.body).to eq 'success' + end + + it 'does not mount the endpoint with a null name' do + get 'api_name_' + expect(last_response.body).not_to eq 'success' + end + + context 'when the expression lives in a namespace' do + subject(:a_remounted_api) do + Class.new(Grape::API) do + namespace :base do + get(mounted { "api_name_#{configuration[:api_name]}" }) do + 'success' + end + end + end + end + + it 'mounts the endpoint with the name' do + get 'base/api_name_a_name' + expect(last_response.body).to eq 'success' + end + + it 'does not mount the endpoint with a null name' do + get 'base/api_name_' + expect(last_response.body).not_to eq 'success' + end + end + end + context 'when executing a standard block within a `mounted` block with all dynamic params' do subject(:a_remounted_api) do Class.new(Grape::API) do @@ -306,6 +354,74 @@ def app end end + context 'a very complex configuration example' do + before do + top_level_api = Class.new(Grape::API) do + remounted_api = Class.new(Grape::API) do + get configuration[:endpoint_name] do + configuration[:response] + end + end + + expression_namespace = mounted { configuration[:namespace].to_s * 2 } + given(mounted { configuration[:should_mount_expressed] != false }) do + namespace expression_namespace do + mount remounted_api, with: { endpoint_name: configuration[:endpoint_name], response: configuration[:endpoint_response] } + end + end + end + root_api.mount top_level_api, with: configuration_options + end + + context 'when the namespace should be mounted' do + let(:configuration_options) do + { + should_mount_expressed: true, + namespace: 'bang', + endpoint_name: 'james', + endpoint_response: 'bond' + } + end + + it 'gets a response' do + get 'bangbang/james' + expect(last_response.body).to eq 'bond' + end + end + + context 'when should be mounted is nil' do + let(:configuration_options) do + { + should_mount_expressed: nil, + namespace: 'bang', + endpoint_name: 'james', + endpoint_response: 'bond' + } + end + + it 'gets a response' do + get 'bangbang/james' + expect(last_response.body).to eq 'bond' + end + end + + context 'when it should not be mounted' do + let(:configuration_options) do + { + should_mount_expressed: false, + namespace: 'bang', + endpoint_name: 'james', + endpoint_response: 'bond' + } + end + + it 'gets a response' do + get 'bangbang/james' + expect(last_response.body).not_to eq 'bond' + end + end + end + context 'when the configuration is read in a helper' do subject(:a_remounted_api) do Class.new(Grape::API) do From 0a10ee4a60a8b690502b25150ae2b061d9829e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Belle?= <48434445+francois-belle@users.noreply.github.com> Date: Fri, 15 Nov 2019 14:49:41 +0100 Subject: [PATCH 137/290] Documentation hint for mount configuration Add a documentation hint for mount configuration. --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index c5a9a46a2..72d8f6ec7 100644 --- a/README.md +++ b/README.md @@ -456,6 +456,15 @@ class Comment::API < Grape::API end ``` +Note that if you're passing a hash as the first parameter to `mount`, you will need to explicitly put `()` around parameters: +```ruby +# good +mount({ ::Some::Api => '/some/api' }, with: { condition: true }) + +# bad +mount ::Some::Api => '/some/api', with: { condition: true } +``` + You can access `configuration` on the class (to use as dynamic attributes), inside blocks (like namespace) If you want logic happening given on an `configuration`, you can use the helper `given`. From 512d4e3494dbd9804a87247f07e2cdd9c3629aef Mon Sep 17 00:00:00 2001 From: Mikkel Malmberg Date: Mon, 25 Nov 2019 17:24:35 +0100 Subject: [PATCH 138/290] Add ElasticAPM to Monitoring products list (#1935) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 72d8f6ec7..ace28234b 100644 --- a/README.md +++ b/README.md @@ -3851,6 +3851,7 @@ Grape integrates with following third-party tools: * **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/) * **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape) +* **[ElasticAPM](https://www.elastic.co/products/apm) - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape) ## Contributing to Grape From c1204b7a2d62c72b20132937829d6384ea70ee83 Mon Sep 17 00:00:00 2001 From: dblock Date: Fri, 29 Nov 2019 09:19:08 -0500 Subject: [PATCH 139/290] Fix bloat in released gem. --- CHANGELOG.md | 1 + grape.gemspec | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b1977fa..e353127de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * [#1903](https://github.com/ruby-grape/grape/pull/1903): Allow nested params renaming (Hash/Array) - [@bikolya](https://github.com/bikolya). * [#1913](https://github.com/ruby-grape/grape/pull/1913): Fix multiple params validators to return correct messages for nested params - [@bikolya](https://github.com/bikolya). * [#1926](https://github.com/ruby-grape/grape/pull/1926): Fixes configuration within given or mounted blocks - [@myxoh](https://github.com/myxoh). +* [#1937](https://github.com/ruby-grape/grape/pull/1937): Fix bloat in released gem - [@dblock](https://github.com/dblock). ### 1.2.4 (2019/06/13) diff --git a/grape.gemspec b/grape.gemspec index a483dd8b7..c3b5dec30 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -19,7 +19,9 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'rack-accept' s.add_runtime_dependency 'virtus', '>= 1.0.0' - s.files = Dir['**/*'].keep_if { |file| File.file?(file) } + s.files = %w[CHANGELOG.md CONTRIBUTING.md README.md grape.png UPGRADING.md LICENSE] + s.files += %w[grape.gemspec] + s.files += Dir['lib/**/*'] s.test_files = Dir['spec/**/*'] s.require_paths = ['lib'] end From 5d90f4d8c384f6ab39aae0133057ea2f3d9f8455 Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 1 Dec 2019 11:18:50 -0500 Subject: [PATCH 140/290] Preparing for release, 1.2.5. --- CHANGELOG.md | 4 +--- README.md | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e353127de..e661143a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ -### 1.2.5 (Next) +### 1.2.5 (2019/12/01) #### Features -* Your contribution here. * [#1931](https://github.com/ruby-grape/grape/pull/1931): Introduces LazyBlock to generate expressions that will executed at mount time - [@myxoh](https://github.com/myxoh). * [#1918](https://github.com/ruby-grape/grape/pull/1918): Helper methods to access controller context from middleware - [@NikolayRys](https://github.com/NikolayRys). * [#1915](https://github.com/ruby-grape/grape/pull/1915): Micro optimizations in allocating hashes and arrays - [@dnesteryuk](https://github.com/dnesteryuk). @@ -12,7 +11,6 @@ #### Fixes -* Your contribution here. * [#1917](https://github.com/ruby-grape/grape/pull/1917): Update access to rack constant - [@NikolayRys](https://github.com/NikolayRys). * [#1916](https://github.com/ruby-grape/grape/pull/1916): Drop old appraisals - [@NikolayRys](https://github.com/NikolayRys). * [#1911](https://github.com/ruby-grape/grape/pull/1911): Make sure `Grape::Valiations::AtLeastOneOfValidator` properly treats nested params in errors - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/README.md b/README.md index ace28234b..ae4c3b166 100644 --- a/README.md +++ b/README.md @@ -154,9 +154,8 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.5**. +You're reading the documentation for the stable release of Grape, **1.2.5**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.2.4](https://github.com/ruby-grape/grape/blob/v1.2.4/README.md). ## Project Resources From 4a3bde47b65e1f883ba69a267ec7e0579002df3a Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 1 Dec 2019 11:21:05 -0500 Subject: [PATCH 141/290] Preparing for next developer iteration, 1.2.6. --- CHANGELOG.md | 10 ++++++++++ README.md | 3 ++- RELEASING.md | 10 ++++++++-- lib/grape/version.rb | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e661143a0..2246c3b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.2.6 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.2.5 (2019/12/01) #### Features diff --git a/README.md b/README.md index ae4c3b166..ab6343b0c 100644 --- a/README.md +++ b/README.md @@ -154,8 +154,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, **1.2.5**. +You're reading the documentation for the next release of Grape, which should be **1.2.6**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.2.5](https://github.com/ruby-grape/grape/blob/v1.2.5/README.md). ## Project Resources diff --git a/RELEASING.md b/RELEASING.md index 22540fa59..1d40997ce 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -74,7 +74,13 @@ The current stable release is [0.6.0](https://github.com/ruby-grape/grape/blob/v Add the next release to [CHANGELOG.md](CHANGELOG.md). ``` -#### 0.6.1 (Next) +### 0.6.1 (Next) + +#### Features + +* Your contribution here. + +#### Fixes * Your contribution here. ``` @@ -83,7 +89,7 @@ Bump the minor version in lib/grape/version.rb. ```ruby module Grape - VERSION = '0.6.1' + VERSION = '0.6.1'.freeze end ``` diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 882309c43..1b220b89f 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,4 @@ module Grape # The current version of Grape. - VERSION = '1.2.5'.freeze + VERSION = '1.2.6'.freeze end From da3300837c77d3d87d70e1c9868d141107873a7a Mon Sep 17 00:00:00 2001 From: Orien Madgwick <_@orien.io> Date: Mon, 2 Dec 2019 08:00:20 +1100 Subject: [PATCH 142/290] Add project metadata to the gemspec As per https://guides.rubygems.org/specification-reference/#metadata, add metadata to the gemspec file. This'll allow people to more easily access the source code, raise issues and read the changelog. These `bug_tracker_uri`, `changelog_uri`, `documentation_uri` and `source_code_uri` links will appear on the rubygems page at https://rubygems.org/gems/grape and be available via the rubygems API after the next release. --- CHANGELOG.md | 1 + grape.gemspec | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2246c3b0c..fbd8fb548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien). #### Fixes diff --git a/grape.gemspec b/grape.gemspec index c3b5dec30..1e5bb712d 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -11,6 +11,12 @@ Gem::Specification.new do |s| s.summary = 'A simple Ruby framework for building REST-like APIs.' s.description = 'A Ruby framework for rapid API development with great conventions.' s.license = 'MIT' + s.metadata = { + 'bug_tracker_uri' => 'https://github.com/ruby-grape/grape/issues', + 'changelog_uri' => "https://github.com/ruby-grape/grape/blob/v#{s.version}/CHANGELOG.md", + 'documentation_uri' => "https://www.rubydoc.info/gems/grape/#{s.version}", + 'source_code_uri' => "https://github.com/ruby-grape/grape/tree/v#{s.version}" + } s.add_runtime_dependency 'activesupport' s.add_runtime_dependency 'builder' From 9b352a88ff3a4970af7bfa3c633c09e956c2e8ea Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Fri, 4 Oct 2019 10:07:53 +0300 Subject: [PATCH 143/290] replace Virtus with dry-types As we know Virtus isn't maintained anymore, so it makes sense to use something different for coercing. There is a dry-types lib which was created as a replacement for Virtus (at least a part of it). Although, it is only about coercion. This change gets rid of Virtus and adds dry-types to the stack. The structure inside didn't change match. Dry-types was integrated without huge refactoring. If people want to use Virtus for custom types, it is possible after implementing a parse method which initializes a model. class User include Virtus.model attribute :id, Integer attribute :name, String def self.parse(*args) new(*args) end end --- .travis.yml | 26 ++--- CHANGELOG.md | 3 +- README.md | 4 +- UPGRADING.md | 43 +++++++ grape.gemspec | 3 +- lib/grape.rb | 2 - lib/grape/dsl/helpers.rb | 2 +- lib/grape/validations/params_scope.rb | 4 +- lib/grape/validations/types.rb | 35 +----- lib/grape/validations/types/array_coercer.rb | 54 +++++++++ lib/grape/validations/types/build_coercer.rb | 91 +++++++-------- .../validations/types/custom_type_coercer.rb | 62 +++------- .../types/custom_type_collection_coercer.rb | 33 ++---- .../validations/types/dry_type_coercer.rb | 39 +++++++ lib/grape/validations/types/file.rb | 17 ++- lib/grape/validations/types/json.rb | 17 +-- .../types/multiple_type_coercer.rb | 45 ++------ .../validations/types/primitive_coercer.rb | 56 +++++++++ lib/grape/validations/types/set_coercer.rb | 36 ++++++ .../types/variant_collection_coercer.rb | 14 +-- .../types/virtus_collection_patch.rb | 16 --- lib/grape/validations/validators/coerce.rb | 67 ++++++----- .../api/defines_boolean_in_params_spec.rb | 6 +- spec/grape/api_spec.rb | 2 +- spec/grape/dsl/helpers_spec.rb | 4 +- spec/grape/validations/params_scope_spec.rb | 2 +- spec/grape/validations/types_spec.rb | 41 +------ .../validations/validators/coerce_spec.rb | 107 +++++++----------- .../validators/except_values_spec.rb | 2 +- .../validations/validators/presence_spec.rb | 28 +++++ 30 files changed, 473 insertions(+), 388 deletions(-) create mode 100644 lib/grape/validations/types/array_coercer.rb create mode 100644 lib/grape/validations/types/dry_type_coercer.rb create mode 100644 lib/grape/validations/types/primitive_coercer.rb create mode 100644 lib/grape/validations/types/set_coercer.rb delete mode 100644 lib/grape/validations/types/virtus_collection_patch.rb diff --git a/.travis.yml b/.travis.yml index 1626ab6f3..194f08d64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,45 +8,41 @@ gemfile: matrix: include: - - rvm: 2.5.3 + - rvm: 2.6.5 script: - bundle exec danger - - rvm: 2.5.3 + - rvm: 2.6.5 gemfile: Gemfile - - rvm: 2.5.3 + - rvm: 2.6.5 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.5.3 + - rvm: 2.6.5 gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.5.3 + - rvm: 2.6.5 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.5.3 + - rvm: 2.6.5 gemfile: gemfiles/multi_json.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_json - - rvm: 2.5.3 + - rvm: 2.6.5 gemfile: gemfiles/multi_xml.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_xml - - rvm: 2.4.5 + - rvm: 2.5.7 gemfile: Gemfile - - rvm: 2.4.5 + - rvm: 2.5.7 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.3.8 + - rvm: 2.4.9 gemfile: Gemfile - - rvm: 2.3.8 + - rvm: 2.4.9 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.2.10 - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 allow_failures: - - rvm: 2.2.10 - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 - - rvm: 2.5.3 - gemfile: gemfiles/rack_edge.gemfile bundler_args: --without development diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd8fb548..458852b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ -### 1.2.6 (Next) +### 1.3.0 (Next) #### Features * Your contribution here. * [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien). +* [#1920](https://github.com/ruby-grape/grape/pull/1920): Replace Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk). #### Fixes diff --git a/README.md b/README.md index ab6343b0c..231ecd3d0 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.2.6**. +You're reading the documentation for the next release of Grape, which should be **1.3.0**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. The current stable release is [1.2.5](https://github.com/ruby-grape/grape/blob/v1.2.5/README.md). @@ -167,6 +167,8 @@ The current stable release is [1.2.5](https://github.com/ruby-grape/grape/blob/v ## Installation +Ruby 2.4 or newer is required. + Grape is available as a gem, to install it just install the gem: gem install grape diff --git a/UPGRADING.md b/UPGRADING.md index a220a468e..ff65254e6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,49 @@ Upgrading Grape =============== +### Upgrading to >= 1.3.0 + +#### Ruby + +After adding dry-types, Ruby 2.4 or newer is required. + +#### Coercion + +[Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus, explicitly add it to your `Gemfile`. Also, if Virtus is used for defining custom types + +```ruby +class User + include Virtus.model + + attribute :id, Integer + attribute :name, String +end + +# somewhere in your API +params do + requires :user, type: User +end +``` + +Add a class-level `parse` method to the model: + +```ruby +class User + include Virtus.model + + attribute :id, Integer + attribute :name, String + + def self.parse(attrs) + new(attrs) + end +end +``` + +Custom types which don't depend on Virtus don't require any changes. + +For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920). + ### Upgrading to >= 1.2.4 #### Headers in `error!` call diff --git a/grape.gemspec b/grape.gemspec index 1e5bb712d..9062ae7ea 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -20,14 +20,15 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'activesupport' s.add_runtime_dependency 'builder' + s.add_runtime_dependency 'dry-types', '~> 1.1.1' s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0' s.add_runtime_dependency 'rack', '>= 1.3.0' s.add_runtime_dependency 'rack-accept' - s.add_runtime_dependency 'virtus', '>= 1.0.0' s.files = %w[CHANGELOG.md CONTRIBUTING.md README.md grape.png UPGRADING.md LICENSE] s.files += %w[grape.gemspec] s.files += Dir['lib/**/*'] s.test_files = Dir['spec/**/*'] s.require_paths = ['lib'] + s.required_ruby_version = '>= 2.4.0' end diff --git a/lib/grape.rb b/lib/grape.rb index e211e046e..048ea2e66 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -20,8 +20,6 @@ require 'i18n' require 'thread' -require 'virtus' - I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__) module Grape diff --git a/lib/grape/dsl/helpers.rb b/lib/grape/dsl/helpers.rb index d58e2cf02..718511799 100644 --- a/lib/grape/dsl/helpers.rb +++ b/lib/grape/dsl/helpers.rb @@ -65,7 +65,7 @@ def include_all_in_scope def define_boolean_in_mod(mod) return if defined? mod::Boolean - mod.const_set('Boolean', Virtus::Attribute::Boolean) + mod.const_set('Boolean', Grape::API::Boolean) end def inject_api_helpers_to_mod(mod, &_block) diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 75a287c7f..c1192e580 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -427,8 +427,8 @@ def validate_value_coercion(coerce_type, *values_list) values_list.each do |values| next if !values || values.is_a?(Proc) value_types = values.is_a?(Range) ? [values.begin, values.end] : values - if coerce_type == Virtus::Attribute::Boolean - value_types = value_types.map { |type| Virtus::Attribute.build(type) } + if coerce_type == Grape::API::Boolean + value_types = value_types.map { |type| Grape::API::Boolean.build(type) } end unless value_types.all? { |v| v.is_a? coerce_type } raise Grape::Exceptions::IncompatibleOptionValues.new(:type, coerce_type, :values, values) diff --git a/lib/grape/validations/types.rb b/lib/grape/validations/types.rb index 9320668f0..ee2ab22ed 100644 --- a/lib/grape/validations/types.rb +++ b/lib/grape/validations/types.rb @@ -6,10 +6,6 @@ require_relative 'types/json' require_relative 'types/file' -# Patch for Virtus::Attribute::Collection -# See the file for more details -require_relative 'types/virtus_collection_patch' - module Grape module Validations # Module for code related to grape's system for @@ -27,8 +23,7 @@ module Types # a parameter value could not be coerced. class InvalidValue; end - # Types representing a single value, which are coerced through Virtus - # or special logic in Grape. + # Types representing a single value, which are coerced. PRIMITIVES = [ # Numerical Integer, @@ -42,10 +37,12 @@ class InvalidValue; end Time, # Misc - Virtus::Attribute::Boolean, + Grape::API::Boolean, String, Symbol, - Rack::Multipart::UploadedFile + Rack::Multipart::UploadedFile, + TrueClass, + FalseClass ].freeze # Types representing data structures. @@ -86,8 +83,6 @@ def self.primitive?(type) # @param type [Class] type to check # @return [Boolean] whether or not the type is known by Grape as a valid # data structure type - # @note This method does not yet consider 'complex types', which inherit - # Virtus.model. def self.structure?(type) STRUCTURES.include?(type) end @@ -104,25 +99,6 @@ def self.multiple?(type) (type.is_a?(Array) || type.is_a?(Set)) && type.size > 1 end - # Does the given class implement a type system that Grape - # (i.e. the underlying virtus attribute system) supports - # out-of-the-box? Currently supported are +axiom-types+ - # and +virtus+. - # - # The type will be passed to +Virtus::Attribute.build+, - # and the resulting attribute object will be expected to - # respond correctly to +coerce+ and +value_coerced?+. - # - # @param type [Class] type to check - # @return [Boolean] +true+ where the type is recognized - def self.recognized?(type) - return false if type.is_a?(Array) || type.is_a?(Set) - - type.is_a?(Virtus::Attribute) || - type.ancestors.include?(Axiom::Types::Type) || - type.include?(Virtus::Model::Core) - end - # Does Grape provide special coercion and validation # routines for the given class? This does not include # automatic handling for primitives, structures and @@ -152,7 +128,6 @@ def self.custom?(type) !primitive?(type) && !structure?(type) && !multiple?(type) && - !recognized?(type) && !special?(type) && type.respond_to?(:parse) && type.method(:parse).arity == 1 diff --git a/lib/grape/validations/types/array_coercer.rb b/lib/grape/validations/types/array_coercer.rb new file mode 100644 index 000000000..57174fa68 --- /dev/null +++ b/lib/grape/validations/types/array_coercer.rb @@ -0,0 +1,54 @@ +require_relative 'dry_type_coercer' + +module Grape + module Validations + module Types + # Coerces elements in an array. It might be an array of strings or integers or + # anything else. + # + # It could've been possible to use an +of+ + # method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/) + # provided by dry-types. Unfortunately, it doesn't work for Grape because of + # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer` + # maintains Virtus behavior in coercing. + class ArrayCoercer < DryTypeCoercer + def initialize(type, strict = false) + super + + @coercer = scope::Array + @elem_coercer = PrimitiveCoercer.new(type.first, strict) + end + + def call(_val) + collection = super + + return collection if collection.is_a?(InvalidValue) + + coerce_elements collection + end + + protected + + def coerce_elements(collection) + collection.each_with_index do |elem, index| + return InvalidValue.new if reject?(elem) + + coerced_elem = @elem_coercer.call(elem) + + return coerced_elem if coerced_elem.is_a?(InvalidValue) + + collection[index] = coerced_elem + end + + collection + end + + # This method maintaine logic which was defined by Virtus for arrays. + # Virtus doesn't allow nil in arrays. + def reject?(val) + val.nil? + end + end + end + end +end diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index 2a8e9968c..9ed1e5d6f 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -1,78 +1,73 @@ +require_relative 'array_coercer' +require_relative 'set_coercer' +require_relative 'primitive_coercer' + module Grape module Validations module Types - # Work out the +Virtus::Attribute+ object to - # use for coercing strings to the given +type+. - # Coercion +method+ will be inferred if none is - # supplied. + # Chooses the best coercer for the given type. For example, if the type + # is Integer, it will return a coercer which will be able to coerce a value + # to the integer. + # + # There are a few very special coercers which might be returned. + # + # +Grape::Types::MultipleTypeCoercer+ is a coercer which is returned when + # the given type implies values in an array with different types. + # For example, +[Integer, String]+ allows integer and string values in + # an array. + # + # +Grape::Types::CustomTypeCoercer+ is a coercer which is returned when + # a method is specified by a user with +coerce_with+ option or the user + # specifies a custom type which implements requirments of + # +Grape::Types::CustomTypeCoercer+. # - # If a +Virtus::Attribute+ object already built - # with +Virtus::Attribute.build+ is supplied as - # the +type+ it will be returned and +method+ - # will be ignored. + # +Grape::Types::CustomTypeCollectionCoercer+ is a very similar to the + # previous one, but it expects an array or set of values having a custom + # type implemented by the user. # - # See {CustomTypeCoercer} for further details - # about coercion and type-checking inference. + # There is also a group of custom types implemented by Grape, check + # +Grape::Validations::Types::SPECIAL+ to get the full list. # # @param type [Class] the type to which input strings # should be coerced # @param method [Class,#call] the coercion method to use - # @return [Virtus::Attribute] object to be used + # @return [Object] object to be used # for coercion and type validation - def self.build_coercer(type, method = nil) - cache_instance(type, method) do - create_coercer_instance(type, method) + def self.build_coercer(type, method: nil, strict: false) + cache_instance(type, method, strict) do + create_coercer_instance(type, method, strict) end end - def self.create_coercer_instance(type, method = nil) - # Accept pre-rolled virtus attributes without interference - return type if type.is_a? Virtus::Attribute - - converter_options = { - nullify_blank: true - } - conversion_type = if method == JSON - Object - # because we want just parsed JSON content: - # if type is Array and data is `"{}"` - # result will be [] because Virtus converts hashes - # to arrays - else - type - end - + def self.create_coercer_instance(type, method, strict) # Use a special coercer for multiply-typed parameters. if Types.multiple?(type) - converter_options[:coercer] = Types::MultipleTypeCoercer.new(type, method) - conversion_type = Object + MultipleTypeCoercer.new(type, method) # Use a special coercer for custom types and coercion methods. elsif method || Types.custom?(type) - converter_options[:coercer] = Types::CustomTypeCoercer.new(type, method) + CustomTypeCoercer.new(type, method) # Special coercer for collections of types that implement a parse method. # CustomTypeCoercer (above) already handles such types when an explicit coercion # method is supplied. elsif Types.collection_of_custom?(type) - converter_options[:coercer] = Types::CustomTypeCollectionCoercer.new( + Types::CustomTypeCollectionCoercer.new( type.first, type.is_a?(Set) ) - - # Grape swaps in its own Virtus::Attribute implementations - # for certain special types that merit first-class support - # (but not if a custom coercion method has been supplied). elsif Types.special?(type) - conversion_type = Types::SPECIAL[type] + Types::SPECIAL[type].new + elsif type.is_a?(Array) + ArrayCoercer.new type, strict + elsif type.is_a?(Set) + SetCoercer.new type, strict + else + PrimitiveCoercer.new type, strict end - - # Virtus will infer coercion and validation rules - # for many common ruby types. - Virtus::Attribute.build(conversion_type, converter_options) end - def self.cache_instance(type, method, &_block) - key = cache_key(type, method) + def self.cache_instance(type, method, strict, &_block) + key = cache_key(type, method, strict) return @__cache[key] if @__cache.key?(key) @@ -85,8 +80,8 @@ def self.cache_instance(type, method, &_block) instance end - def self.cache_key(type, method) - [type, method].compact.map(&:to_s).join('_') + def self.cache_key(type, method, strict) + [type, method, strict].compact.map(&:to_s).join('_') end instance_variable_set(:@__cache, {}) diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index f6b26cf83..9a6b4da18 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -1,21 +1,6 @@ module Grape module Validations module Types - # Instances of this class may be passed to - # +Virtus::Attribute.build+ as the +:coercer+ - # option for custom types that do not otherwise - # satisfy the requirements for +Virtus::Attribute::coerce+ - # and +Virtus::Attribute::value_coerced?+ to work - # as expected. - # - # Subclasses of +Virtus::Attribute+ or +Axiom::Types::Type+ - # (or for which an axiom type can be inferred, i.e. the - # primitives, +Date+, +Time+, etc.) do not need any such - # coercer to be passed with them. - # - # Coercion - # -------- - # # This class will detect type classes that implement # a class-level +parse+ method. The method should accept one # +String+ argument and should return the value coerced to @@ -30,14 +15,14 @@ module Types # Type Checking # ------------- # - # Calls to +value_coerced?+ will consult this class to check + # Calls to +coerced?+ will consult this class to check # that the coerced value produced above is in fact of the # expected type. By default this class performs a basic check # against the type supplied, but this behaviour will be # overridden if the class implements a class-level # +coerced?+ or +parsed?+ method. This method # will receive a single parameter that is the coerced value - # and should return +true+ iff the value meets type expectations. + # and should return +true+ if the value meets type expectations. # Arbitrary assertions may be made here but the grape validation # system should be preferred. # @@ -46,15 +31,6 @@ module Types # contract as +coerced?+, and must be supplied with a coercion # +method+. class CustomTypeCoercer - # Uses +Virtus::Attribute.build+ to build a new - # attribute that makes use of this class for - # coercion and type validation logic. - # - # @return [Virtus::Attribute] - def self.build(type, method = nil) - Virtus::Attribute.build(type, coercer: new(type, method)) - end - # A new coercer for the given type specification # and coercion method. # @@ -64,37 +40,25 @@ def self.build(type, method = nil) # optional coercion method. See class docs. def initialize(type, method = nil) coercion_method = infer_coercion_method type, method - @method = enforce_symbolized_keys type, coercion_method - @type_check = infer_type_check(type) end - # This method is called from somewhere within - # +Virtus::Attribute::coerce+ in order to coerce - # the given value. + # Coerces the given value. # # @param value [String] value to be coerced, in grape # this should always be a string. # @return [Object] the coerced result - def call(value) - @method.call value + def call(val) + return if val.nil? + + coerced_val = @method.call(val) + return InvalidValue.new unless coerced?(coerced_val) + coerced_val end - # This method is called from somewhere within - # +Virtus::Attribute::value_coerced?+ in order to - # assert that the value has been coerced successfully. - # - # @param _primitive [Axiom::Types::Type] primitive type - # for the coercion as detected by axiom-types' inference - # system. For custom types this is typically not much use - # (i.e. it is +Axiom::Types::Object+) unless special - # inference rules have been declared for the type. - # @param value [Object] a coerced result returned from {#call} - # @return [true,false] whether or not the coerced value - # satisfies type requirements. - def success?(_primitive, value) - @type_check.call value + def coerced?(val) + @type_check.call val end private @@ -160,8 +124,8 @@ def enforce_symbolized_keys(type, method) # Collections have all values processed individually if [Array, Set].include?(type) lambda do |val| - method.call(val).tap do |new_value| - new_value.map do |item| + method.call(val).tap do |new_val| + new_val.map do |item| item.is_a?(Hash) ? symbolize_keys(item) : item end end diff --git a/lib/grape/validations/types/custom_type_collection_coercer.rb b/lib/grape/validations/types/custom_type_collection_coercer.rb index 7534420fe..ebdae4d40 100644 --- a/lib/grape/validations/types/custom_type_collection_coercer.rb +++ b/lib/grape/validations/types/custom_type_collection_coercer.rb @@ -1,12 +1,6 @@ module Grape module Validations module Types - # Instances of this class may be passed to - # +Virtus::Attribute.build+ as the +:coercer+ - # option, to handle collections of types that - # provide their own parsing (and optionally, - # type-checking) functionality. - # # See {CustomTypeCoercer} for details on types # that will be supported by this by this coercer. # This coercer works in the same way as +CustomTypeCoercer+ @@ -38,32 +32,21 @@ def initialize(type, set = false) @set = set end - # This method is called from somewhere within - # +Virtus::Attribute::coerce+ in order to coerce - # the given value. + # Coerces the given value. # # @param value [Array] an array of values to be coerced # @return [Array,Set] the coerced result. May be an +Array+ or a # +Set+ depending on the setting given to the constructor def call(value) - coerced = value.map { |item| super(item) } + coerced = value.map do |item| + coerced_item = super(item) - @set ? Set.new(coerced) : coerced - end + return coerced_item if coerced_item.is_a?(InvalidValue) - # This method is called from somewhere within - # +Virtus::Attribute::value_coerced?+ in order to assert - # that the all of the values in the array have been coerced - # successfully. - # - # @param primitive [Axiom::Types::Type] primitive type for - # the coercion as deteced by axiom-types' inference system. - # @param value [Enumerable] a coerced result returned from {#call} - # @return [true,false] whether or not all of the coerced values in - # the collection satisfy type requirements. - def success?(primitive, value) - value.is_a?(@set ? Set : Array) && - value.all? { |item| super(primitive, item) } + coerced_item + end + + @set ? Set.new(coerced) : coerced end end end diff --git a/lib/grape/validations/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb new file mode 100644 index 000000000..a0a9841c1 --- /dev/null +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -0,0 +1,39 @@ +require 'dry-types' + +module DryTypes + # Call +Dry.Types()+ to add all registered types to +DryTypes+ which is + # a container in this case. Check documentation for more information + # https://dry-rb.org/gems/dry-types/1.2/getting-started/ + include Dry.Types() +end + +module Grape + module Validations + module Types + # A base class for classes which must identify a coercer to be used. + # If the +strict+ argument is true, it won't coerce the given value + # but check its type. More information there + # https://dry-rb.org/gems/dry-types/1.2/built-in-types/ + class DryTypeCoercer + def initialize(type, strict = false) + @type = type + @scope = strict ? DryTypes::Strict : DryTypes::Params + end + + # Coerces the given value to a type which was specified during + # initialization as a type argument. + # + # @param val [Object] + def call(val) + @coercer[val] + rescue Dry::Types::CoercionError => _e + InvalidValue.new + end + + protected + + attr_reader :scope, :type + end + end + end +end diff --git a/lib/grape/validations/types/file.rb b/lib/grape/validations/types/file.rb index 62aa3f694..0a47bc370 100644 --- a/lib/grape/validations/types/file.rb +++ b/lib/grape/validations/types/file.rb @@ -1,21 +1,20 @@ module Grape module Validations module Types - # +Virtus::Attribute+ implementation for parameters - # that are multipart file objects. Actual handling - # of these objects is provided by +Rack::Request+; - # this class is here only to assert that rack's - # handling has succeeded, and to prevent virtus - # from interfering. - class File < Virtus::Attribute - def coerce(input) + # Implementation for parameters that are multipart file objects. + # Actual handling of these objects is provided by +Rack::Request+; + # this class is here only to assert that rack's handling has succeeded. + class File + def call(input) + return InvalidValue.new unless coerced?(input) + # Processing of multipart file objects # is already taken care of by Rack::Request. # Nothing to do here. input end - def value_coerced?(value) + def coerced?(value) # Rack::Request creates a Hash with filename, # content type and an IO object. Do a bit of basic # duck-typing. diff --git a/lib/grape/validations/types/json.rb b/lib/grape/validations/types/json.rb index 220d1db6d..c464477b1 100644 --- a/lib/grape/validations/types/json.rb +++ b/lib/grape/validations/types/json.rb @@ -3,19 +3,20 @@ module Grape module Validations module Types - # +Virtus::Attribute+ implementation that handles coercion - # and type checking for parameters that are complex types - # given as JSON-encoded strings. It accepts both JSON objects + # Handles coercion and type checking for parameters that are complex + # types given as JSON-encoded strings. It accepts both JSON objects # and arrays of objects, and will coerce the input to a +Hash+ # or +Array+ object respectively. In either case the Grape # validation system will apply nested validation rules to # all returned objects. - class Json < Virtus::Attribute + class Json # Coerce the input into a JSON-like data structure. # # @param input [String] a JSON-encoded parameter value # @return [Hash,Array,nil] - def coerce(input) + def call(input) + return input if coerced?(input) + # Allow nulls and blank strings return if input.nil? || input =~ /^\s*$/ JSON.parse(input, symbolize_names: true) @@ -26,7 +27,7 @@ def coerce(input) # # @param value [Object] result of {#coerce} # @return [true,false] - def value_coerced?(value) + def coerced?(value) value.is_a?(::Hash) || coerced_collection?(value) end @@ -50,13 +51,13 @@ class JsonArray < Json # # @param input [String] JSON-encoded parameter value # @return [Array] - def coerce(input) + def call(input) json = super Array.wrap(json) unless json.nil? end # See {Json#coerced_collection?} - def value_coerced?(value) + def coerced?(value) coerced_collection? value end end diff --git a/lib/grape/validations/types/multiple_type_coercer.rb b/lib/grape/validations/types/multiple_type_coercer.rb index e78745382..215030022 100644 --- a/lib/grape/validations/types/multiple_type_coercer.rb +++ b/lib/grape/validations/types/multiple_type_coercer.rb @@ -22,53 +22,32 @@ def initialize(types, method = nil) @type_coercers = types.map do |type| if Types.multiple? type - VariantCollectionCoercer.new type + VariantCollectionCoercer.new type, @method else - Types.build_coercer type + Types.build_coercer type, strict: !@method.nil? end end end - # This method is called from somewhere within - # +Virtus::Attribute::coerce+ in order to coerce - # the given value. + # Coerces the given value. # - # @param value [String] value to be coerced, in grape + # @param val [String] value to be coerced, in grape # this should always be a string. # @return [Object,InvalidValue] the coerced result, or an instance # of {InvalidValue} if the value could not be coerced. - def call(value) - return @method.call(value) if @method + def call(val) + # once the value is coerced by the custom method, its type should be checked + val = @method.call(val) if @method + + coerced_val = InvalidValue.new @type_coercers.each do |coercer| - coerced = coercer.coerce(value) + coerced_val = coercer.call(val) - return coerced if coercer.value_coerced? coerced + return coerced_val unless coerced_val.is_a?(InvalidValue) end - # Declare that we couldn't coerce the value in such a way - # that Grape won't ask us again if the value is valid - InvalidValue.new - end - - # This method is called from somewhere within - # +Virtus::Attribute::value_coerced?+ in order to - # assert that the value has been coerced successfully. - # Due to Grape's design this will in fact only be called - # if a custom coercion method is being used, since {#call} - # returns an {InvalidValue} object if the value could not - # be coerced. - # - # @param _primitive [Axiom::Types::Type] primitive type - # for the coercion as detected by axiom-types' inference - # system. For custom types this is typically not much use - # (i.e. it is +Axiom::Types::Object+) unless special - # inference rules have been declared for the type. - # @param value [Object] a coerced result returned from {#call} - # @return [true,false] whether or not the coerced value - # satisfies type requirements. - def success?(_primitive, value) - @type_coercers.any? { |coercer| coercer.value_coerced? value } + coerced_val end end end diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb new file mode 100644 index 000000000..d46247bda --- /dev/null +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -0,0 +1,56 @@ +require_relative 'dry_type_coercer' + +module Grape + module Validations + module Types + # Coerces the given value to a type defined via a +type+ argument during + # initialization. + class PrimitiveCoercer < DryTypeCoercer + MAPPING = { + Grape::API::Boolean => DryTypes::Params::Bool, + + # unfortunatelly, a +Params+ scope doesn't contain String + String => DryTypes::Coercible::String + }.freeze + + STRICT_MAPPING = { + Grape::API::Boolean => DryTypes::Strict::Bool + }.freeze + + def initialize(type, strict = false) + super + + @type = type + + @coercer = if strict + STRICT_MAPPING.fetch(type) { scope.const_get(type.name) } + else + MAPPING.fetch(type) { scope.const_get(type.name) } + end + end + + def call(val) + return InvalidValue.new if reject?(val) + return nil if val.nil? + return '' if val == '' + + super + end + + protected + + attr_reader :type + + # This method maintaine logic which was defined by Virtus. For example, + # dry-types is ok to convert an array or a hash to a string, it is supported, + # but Virtus wouldn't accept it. So, this method only exists to not introduce + # breaking changes. + def reject?(val) + (val.is_a?(Array) && type == String) || + (val.is_a?(String) && type == Hash) || + (val.is_a?(Hash) && type == String) + end + end + end + end +end diff --git a/lib/grape/validations/types/set_coercer.rb b/lib/grape/validations/types/set_coercer.rb new file mode 100644 index 000000000..402dd60c1 --- /dev/null +++ b/lib/grape/validations/types/set_coercer.rb @@ -0,0 +1,36 @@ +require 'set' +require_relative 'dry_type_coercer' + +module Grape + module Validations + module Types + # Takes the given array and converts it to a set. Every element of the set + # is also coerced. + class SetCoercer < DryTypeCoercer + def initialize(type, strict = false) + super + + @elem_coercer = PrimitiveCoercer.new(type.first, strict) + end + + def call(value) + return InvalidValue.new unless value.is_a?(Array) + + coerce_elements(value) + end + + protected + + def coerce_elements(collection) + collection.each_with_object(Set.new) do |elem, memo| + coerced_elem = @elem_coercer.call(elem) + + return coerced_elem if coerced_elem.is_a?(InvalidValue) + + memo.add(coerced_elem) + end + end + end + end + end +end diff --git a/lib/grape/validations/types/variant_collection_coercer.rb b/lib/grape/validations/types/variant_collection_coercer.rb index f387457ee..d841b6e55 100644 --- a/lib/grape/validations/types/variant_collection_coercer.rb +++ b/lib/grape/validations/types/variant_collection_coercer.rb @@ -3,7 +3,7 @@ module Validations module Types # This class wraps {MultipleTypeCoercer}, for use with collections # that allow members of more than one type. - class VariantCollectionCoercer < Virtus::Attribute + class VariantCollectionCoercer # Construct a new coercer that will attempt to coerce # a list of values such that all members are of one of # the given types. The container may also optionally be @@ -30,7 +30,7 @@ def initialize(types, method = nil) # @return [Array,Set,InvalidValue] # the coerced result, or an instance # of {InvalidValue} if the value could not be coerced. - def coerce(value) + def call(value) return InvalidValue.new unless value.is_a? Array value = @@ -43,16 +43,6 @@ def coerce(value) value end - - # Assert that the value has been coerced successfully. - # - # @param value [Object] a coerced result returned from {#coerce} - # @return [true,false] whether or not the coerced value - # satisfies type requirements. - def value_coerced?(value) - value.is_a?(@types.class) && - value.all? { |v| @member_coercer.success?(@types, v) } - end end end end diff --git a/lib/grape/validations/types/virtus_collection_patch.rb b/lib/grape/validations/types/virtus_collection_patch.rb deleted file mode 100644 index eab5208b2..000000000 --- a/lib/grape/validations/types/virtus_collection_patch.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'virtus/attribute/collection' - -# See https://github.com/solnic/virtus/pull/343 -# This monkey-patch fixes type validation for collections, -# ensuring that type assertions are applied to collection -# members. -# -# This patch duplicates the code in the above pull request. -# Once the request, or equivalent functionality, has been -# published into the +virtus+ gem this file should be deleted. -Virtus::Attribute::Collection.class_eval do - # @api public - def value_coerced?(value) - super && value.all? { |item| member_type.value_coerced? item } - end -end diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 4638d67f4..f21bbaadf 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -1,9 +1,15 @@ module Grape class API - Boolean = Virtus::Attribute::Boolean + class Boolean + def self.build(val) + return nil if val != true && val != false + + new + end + end class Instance - Boolean = Virtus::Attribute::Boolean + Boolean = Grape::API::Boolean end end @@ -11,7 +17,12 @@ module Validations class CoerceValidator < Base def initialize(*_args) super - @converter = Types.build_coercer(type, @option[:method]) + + @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer) + type + else + Types.build_coercer(type, method: @option[:method]) + end end def validate(request) @@ -19,11 +30,22 @@ def validate(request) end def validate_param!(attr_name, params) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless params.is_a? Hash - return unless requires_coercion?(params[attr_name]) + raise validation_exception(attr_name) unless params.is_a? Hash + new_value = coerce_value(params[attr_name]) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless valid_type?(new_value) - params[attr_name] = new_value + + raise validation_exception(attr_name) unless valid_type?(new_value) + + # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash + # which looses wrappers for hashes and arrays after reassigning values + # + # h = Hashie::Mash.new(list: [1, 2, 3, 4]) + # => #> + # list = h.list + # h[:list] = list + # h + # => # + params[attr_name] = new_value unless params[attr_name] == new_value end private @@ -33,31 +55,25 @@ def validate_param!(attr_name, params) # # See {Types.build_coercer} # - # @return [Virtus::Attribute] + # @return [Object] attr_reader :converter def valid_type?(val) - # Special value to denote coercion failure - return false if val.instance_of?(Types::InvalidValue) - - # Allow nil, to ignore when a parameter is absent - return true if val.nil? - - converter.value_coerced? val + !val.is_a?(Types::InvalidValue) end def coerce_value(val) - # Don't coerce things other than nil to Arrays or Hashes - unless (@option[:method] && !val.nil?) || type.is_a?(Virtus::Attribute) - return val || [] if type == Array - return val || Set.new if type == Set - return val || {} if type == Hash + # define default values for structures, the dry-types lib which is used + # for coercion doesn't accept nil as a value, so it would fail + if val.nil? + return [] if type == Array || type.is_a?(Array) + return Set.new if type == Set + return {} if type == Hash end - converter.coerce(val) + converter.call(val) - # not the prettiest but some invalid coercion can currently trigger - # errors in Virtus (see coerce_spec.rb:75) + # Some custom types might fail, so it should be treated as an invalid value rescue Types::InvalidValue.new end @@ -69,9 +85,8 @@ def type @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type] end - def requires_coercion?(value) - # JSON types do not require coercion if value is valid - !valid_type?(value) || converter.coercer.respond_to?(:method) && !converter.is_a?(Grape::Validations::Types::Json) + def validation_exception(attr_name) + Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:coerce)) end end end diff --git a/spec/grape/api/defines_boolean_in_params_spec.rb b/spec/grape/api/defines_boolean_in_params_spec.rb index 166b2d0a8..d057bbbce 100644 --- a/spec/grape/api/defines_boolean_in_params_spec.rb +++ b/spec/grape/api/defines_boolean_in_params_spec.rb @@ -21,7 +21,7 @@ def app { class: 'TrueClass', value: true }.to_s end - it 'sets Boolean as a Virtus::Attribute::Boolean' do + it 'sets Boolean as a type' do post '/echo?message=true' expect(last_response.status).to eq(201) expect(last_response.body).to eq expected_body @@ -29,8 +29,8 @@ def app context 'Params endpoint type' do subject { DefinesBooleanInstanceSpec::API.new.router.map['POST'].first.options[:params]['message'][:type] } - it 'params type is a Virtus::Attribute::Boolean' do - is_expected.to eq 'Virtus::Attribute::Boolean' + it 'params type is a boolean' do + is_expected.to eq 'Grape::API::Boolean' end end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 63c1623bb..8db185c75 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1133,7 +1133,7 @@ class DummyFormatClass subject.use Rack::Chunked subject.get('/stream') { stream test_stream } - get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1' + get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1' expect(last_response.headers['Content-Type']).to eq('text/plain') expect(last_response.headers['Content-Length']).to eq(nil) diff --git a/spec/grape/dsl/helpers_spec.rb b/spec/grape/dsl/helpers_spec.rb index 6e24cfe00..c1ce8de70 100644 --- a/spec/grape/dsl/helpers_spec.rb +++ b/spec/grape/dsl/helpers_spec.rb @@ -75,9 +75,9 @@ def test end context 'with an external file' do - it 'sets Boolean as a Virtus::Attribute::Boolean' do + it 'sets Boolean as a Grape::API::Boolean' do subject.helpers BooleanParam - expect(subject.first_mod::Boolean).to eq Virtus::Attribute::Boolean + expect(subject.first_mod::Boolean).to eq Grape::API::Boolean end end diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 3658efb87..6faf5680b 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -30,7 +30,7 @@ def app context 'when the default value is false' do before do subject.params do - optional :bool, type: Virtus::Attribute::Boolean, default: false + optional :bool, type: Grape::API::Boolean, default: false end subject.get end diff --git a/spec/grape/validations/types_spec.rb b/spec/grape/validations/types_spec.rb index 353d75fda..1380b1016 100644 --- a/spec/grape/validations/types_spec.rb +++ b/spec/grape/validations/types_spec.rb @@ -11,29 +11,10 @@ def self.parse; end end end - VirtusA = Virtus::Attribute.build(String) - - module VirtusModule - include Virtus.module - end - - class VirtusB - include VirtusModule - end - - class VirtusC - include Virtus.model - end - - MyAxiom = Axiom::Types::String.new do - minimum_length 1 - maximum_length 30 - end - describe '::primitive?' do [ Integer, Float, Numeric, BigDecimal, - Virtus::Attribute::Boolean, String, Symbol, + Grape::API::Boolean, String, Symbol, Date, DateTime, Time, Rack::Multipart::UploadedFile ].each do |type| it "recognizes #{type} as a primitive" do @@ -57,16 +38,6 @@ class VirtusC end end - describe '::recognized?' do - [ - VirtusA, VirtusB, VirtusC, MyAxiom - ].each do |type| - it "recognizes #{type}" do - expect(described_class.recognized?(type)).to be_truthy - end - end - end - describe '::special?' do [ JSON, Array[JSON], File, Rack::Multipart::UploadedFile @@ -97,14 +68,14 @@ class VirtusC expect(described_class.instance_variable_get(:@__cache_write_lock)).to be_a(Mutex) end - it 'caches the result of the Virtus::Attribute.build method' do + it 'caches the result of the build_coercer method' do original_cache = described_class.instance_variable_get(:@__cache) described_class.instance_variable_set(:@__cache, {}) - coercer = 'TestCoercer' - expect(Virtus::Attribute).to receive(:build).once.and_return(coercer) - expect(described_class.build_coercer(Array[String])).to eq(coercer) - expect(described_class.build_coercer(Array[String])).to eq(coercer) + a_coercer = described_class.build_coercer(Array[String]) + b_coercer = described_class.build_coercer(Array[String]) + + expect(a_coercer.object_id).to eq(b_coercer.object_id) described_class.instance_variable_set(:@__cache, original_cache) end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 94f66e873..1c956f6fd 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -10,11 +10,13 @@ def app end describe 'coerce' do - module CoerceValidatorSpec - class User - include Virtus.model - attribute :id, Integer - attribute :name, String + class SecureURIOnly + def self.parse(value) + URI.parse(value) + end + + def self.parsed?(value) + value.is_a? URI::HTTPS end end @@ -96,6 +98,7 @@ class User it 'respects :coerce_with' do get '/', a: 'yup' + expect(last_response.status).to eq(200) expect(last_response.body).to eq('TrueClass') end @@ -148,25 +151,6 @@ class User expect(last_response.body).to eq('array int works') end - context 'complex objects' do - it 'error on malformed input for complex objects' do - subject.params do - requires :user, type: CoerceValidatorSpec::User - end - subject.get '/user' do - 'complex works' - end - - get '/user', user: '32' - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('user is invalid') - - get '/user', user: { id: 32, name: 'Bob' } - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('complex works') - end - end - context 'coerces' do it 'Integer' do subject.params do @@ -181,6 +165,25 @@ class User expect(last_response.body).to eq(integer_class_name) end + it 'is a custom type' do + subject.params do + requires :uri, coerce: SecureURIOnly + end + subject.get '/secure_uri' do + params[:uri].class + end + + get 'secure_uri', uri: 'https://www.example.com' + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('URI::HTTPS') + + get 'secure_uri', uri: 'http://www.example.com' + + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('uri is invalid') + end + context 'Array' do it 'Array of Integers' do subject.params do @@ -197,7 +200,7 @@ class User it 'Array of Bools' do subject.params do - requires :arry, coerce: Array[Virtus::Attribute::Boolean] + requires :arry, coerce: Array[Grape::API::Boolean] end subject.get '/array' do params[:arry][0].class @@ -208,27 +211,6 @@ class User expect(last_response.body).to eq('TrueClass') end - it 'Array of Complex' do - subject.params do - requires :arry, coerce: Array[CoerceValidatorSpec::User] - end - subject.get '/array' do - params[:arry].size - end - - get 'array', arry: [31] - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('arry is invalid') - - get 'array', arry: { id: 31, name: 'Alice' } - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('arry is invalid') - - get 'array', arry: [{ id: 31, name: 'Alice' }] - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('1') - end - it 'Array of type implementing parse' do subject.params do requires :uri, type: Array[URI] @@ -253,17 +235,7 @@ class User expect(last_response.body).to eq('Set,URI::HTTP,1') end - it 'Array of class implementing parse and parsed?' do - class SecureURIOnly - def self.parse(value) - URI.parse(value) - end - - def self.parsed?(value) - value.is_a? URI::HTTPS - end - end - + it 'Array of a custom type' do subject.params do requires :uri, type: Array[SecureURIOnly] end @@ -295,7 +267,7 @@ def self.parsed?(value) it 'Set of Bools' do subject.params do - requires :set, coerce: Set[Virtus::Attribute::Boolean] + requires :set, coerce: Set[Grape::API::Boolean] end subject.get '/set' do params[:set].first.class @@ -309,7 +281,7 @@ def self.parsed?(value) it 'Bool' do subject.params do - requires :bool, coerce: Virtus::Attribute::Boolean + requires :bool, coerce: Grape::API::Boolean end subject.get '/bool' do params[:bool].class @@ -443,19 +415,19 @@ def self.parsed?(value) it 'parses parameters with Array[String] type' do subject.params do - requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) } + requires :values, type: Array[String], coerce_with: ->(val) { val.split(/\s+/) } end - subject.get '/ints' do + subject.get '/strings' do params[:values] end - get '/ints', values: '1 2 3 4' + get '/strings', values: '1 2 3 4' expect(last_response.status).to eq(200) expect(JSON.parse(last_response.body)).to eq(%w[1 2 3 4]) - get '/ints', values: 'a b c d' + get '/strings', values: 'a b c d' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)).to eq(%w[0 0 0 0]) + expect(JSON.parse(last_response.body)).to eq(%w[a b c d]) end it 'parses parameters with Array[Integer] type' do @@ -914,14 +886,17 @@ def self.parsed?(value) end context 'converter' do - it 'does not build Virtus::Attribute multiple times' do + it 'does not build a coercer multiple times' do subject.params do requires :something, type: Array[String] end subject.get do end - expect(Virtus::Attribute).to receive(:build).at_most(2).times.and_call_original + expect(Grape::Validations::Types::ArrayCoercer).to( + receive(:new).at_most(:once).and_call_original + ) + 10.times { get '/' } end end diff --git a/spec/grape/validations/validators/except_values_spec.rb b/spec/grape/validations/validators/except_values_spec.rb index 7030774cc..9e0787697 100644 --- a/spec/grape/validations/validators/except_values_spec.rb +++ b/spec/grape/validations/validators/except_values_spec.rb @@ -110,7 +110,7 @@ def excepts optional: { type: Array[Integer], except_values: [10, 11], default: 12 }, tests: [ { value: 'invalid-type1', rc: 400, body: { error: 'type is invalid' }.to_json }, - { value: 10, rc: 400, body: { error: 'type has a value not allowed' }.to_json }, + { value: 10, rc: 400, body: { error: 'type is invalid' }.to_json }, { value: [10], rc: 400, body: { error: 'type has a value not allowed' }.to_json }, { value: ['3'], rc: 200, body: { type: [3] }.to_json }, { value: [3], rc: 200, body: { type: [3] }.to_json }, diff --git a/spec/grape/validations/validators/presence_spec.rb b/spec/grape/validations/validators/presence_spec.rb index d00163ead..7de97190a 100644 --- a/spec/grape/validations/validators/presence_spec.rb +++ b/spec/grape/validations/validators/presence_spec.rb @@ -269,4 +269,32 @@ def app expect(last_response.body).to eq('Hello optional'.to_json) end end + + context 'with a custom type' do + it 'does not validate their type when it is missing' do + class CustomType + def self.parse(value) + return if value.blank? + + new + end + end + + subject.params do + requires :custom, type: CustomType + end + subject.get '/custom' do + 'custom' + end + + get 'custom' + + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('{"error":"custom is missing"}') + + get 'custom', custom: 'filled' + + expect(last_response.status).to eq(200) + end + end end From 7ed75987bf530b6ccb02f40c1f11fa7d5707c7fd Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 3 Dec 2019 22:28:16 +0200 Subject: [PATCH 144/290] get rid of a needless step in HashWithIndifferentAccess There was an extra operation in `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` it is unnecessary, people already use Rails 4, 5 or 6. Devs who are on Rails 3 less likely will upgrade to latest Grape. After removing that operation Grape consumes less memory. Before: 36080 bytes After: 32472 bytes --- CHANGELOG.md | 1 + lib/grape/dsl/inside_route.rb | 3 ++- lib/grape/endpoint.rb | 2 +- .../hash_with_indifferent_access.rb | 5 ++--- .../deep_hash_with_indifferent_access.rb | 18 ------------------ lib/grape/router/route.rb | 6 ++++-- 6 files changed, 10 insertions(+), 25 deletions(-) delete mode 100644 lib/grape/extensions/deep_hash_with_indifferent_access.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 458852b3b..3948d6510 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1940](https://github.com/ruby-grape/grape/pull/1940): Get rid of a needless step in HashWithIndifferentAccess - [@dnesteryuk](https://github.com/dnesteryuk). * [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien). * [#1920](https://github.com/ruby-grape/grape/pull/1920): Replace Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 2953d590d..0fc5948ad 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -84,7 +84,8 @@ def should_be_empty_array?(declared_param, passed_children_params) end def declared_param_is_array?(declared_param) - route_options_params[declared_param.to_s] && route_options_params[declared_param.to_s][:type] == 'Array' + key = declared_param.to_s + route_options_params[key] && route_options_params[key][:type] == 'Array' end def route_options_params diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 0551ef88d..336db40b1 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -154,7 +154,7 @@ def mount_in(router) methods << Grape::Http::Headers::HEAD end methods.each do |method| - unless route.request_method.to_s.upcase == method + unless route.request_method == method route = Grape::Router::Route.new(method, route.origin, route.attributes.to_h) end router.append(route.apply(self)) diff --git a/lib/grape/extensions/active_support/hash_with_indifferent_access.rb b/lib/grape/extensions/active_support/hash_with_indifferent_access.rb index c6bf2ca1a..105e236f5 100644 --- a/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +++ b/lib/grape/extensions/active_support/hash_with_indifferent_access.rb @@ -14,10 +14,9 @@ def params_builder end def build_params - params = ::ActiveSupport::HashWithIndifferentAccess[rack_params] + params = ::ActiveSupport::HashWithIndifferentAccess.new(rack_params) params.deep_merge!(grape_routing_args) if env[Grape::Env::GRAPE_ROUTING_ARGS] - # TODO: remove, in Rails 4 or later ::ActiveSupport::HashWithIndifferentAccess converts nested Hashes into indifferent access ones - DeepHashWithIndifferentAccess.deep_hash_with_indifferent_access(params) + params end end end diff --git a/lib/grape/extensions/deep_hash_with_indifferent_access.rb b/lib/grape/extensions/deep_hash_with_indifferent_access.rb deleted file mode 100644 index e18601f8d..000000000 --- a/lib/grape/extensions/deep_hash_with_indifferent_access.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Grape - module Extensions - module DeepHashWithIndifferentAccess - def self.deep_hash_with_indifferent_access(object) - case object - when ::Hash - object.inject(::ActiveSupport::HashWithIndifferentAccess.new) do |new_hash, (key, value)| - new_hash.merge!(key => deep_hash_with_indifferent_access(value)) - end - when ::Array - object.map { |element| deep_hash_with_indifferent_access(element) } - else - object - end - end - end - end -end diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 52e0098b3..0eff8607f 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -62,10 +62,12 @@ def route_path end def initialize(method, pattern, **options) + upcased_method = method.to_s.upcase + @suffix = options[:suffix] - @options = options.merge(method: method.to_s.upcase) + @options = options.merge(method: upcased_method) @pattern = Pattern.new(pattern, **options) - @translator = AttributeTranslator.new(**options, request_method: method.to_s.upcase) + @translator = AttributeTranslator.new(**options, request_method: upcased_method) end def exec(env) From f1f4e0d56077db5834653db918a6f69088f750c0 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 9 Dec 2019 18:31:51 +0100 Subject: [PATCH 145/290] Rubocop target version now 2.4 (following gemspec) Rubocop autocorrect frozen_string_literal Contribution Fixed frozen_string in subject Fix frozen body in spec --- .rubocop.yml | 2 +- .rubocop_todo.yml | 56 ++++++++++++++++--- Appraisals | 2 + CHANGELOG.md | 1 + Dangerfile | 2 + Gemfile | 2 + Guardfile | 2 + Rakefile | 2 + benchmark/nested_params.rb | 2 + benchmark/simple.rb | 2 + benchmark/simple_with_type_coercer.rb | 2 + gemfiles/multi_json.gemfile | 2 + gemfiles/multi_xml.gemfile | 2 + gemfiles/rack_edge.gemfile | 2 + gemfiles/rails_5.gemfile | 2 + gemfiles/rails_edge.gemfile | 2 + grape.gemspec | 2 + lib/grape.rb | 2 + lib/grape/api.rb | 2 + lib/grape/api/helpers.rb | 2 + lib/grape/api/instance.rb | 2 + lib/grape/config.rb | 2 + lib/grape/cookies.rb | 2 + lib/grape/dsl/api.rb | 2 + lib/grape/dsl/callbacks.rb | 2 + lib/grape/dsl/configuration.rb | 2 + lib/grape/dsl/desc.rb | 2 + lib/grape/dsl/headers.rb | 2 + lib/grape/dsl/helpers.rb | 2 + lib/grape/dsl/inside_route.rb | 2 + lib/grape/dsl/logger.rb | 2 + lib/grape/dsl/middleware.rb | 2 + lib/grape/dsl/parameters.rb | 2 + lib/grape/dsl/request_response.rb | 2 + lib/grape/dsl/routing.rb | 2 + lib/grape/dsl/settings.rb | 2 + lib/grape/dsl/validations.rb | 2 + lib/grape/eager_load.rb | 2 + lib/grape/endpoint.rb | 2 + lib/grape/error_formatter.rb | 2 + lib/grape/error_formatter/base.rb | 2 + lib/grape/error_formatter/json.rb | 2 + lib/grape/error_formatter/txt.rb | 2 + lib/grape/error_formatter/xml.rb | 2 + lib/grape/exceptions/base.rb | 8 ++- .../exceptions/incompatible_option_values.rb | 2 + lib/grape/exceptions/invalid_accept_header.rb | 2 + lib/grape/exceptions/invalid_formatter.rb | 2 + lib/grape/exceptions/invalid_message_body.rb | 2 + lib/grape/exceptions/invalid_response.rb | 2 + .../exceptions/invalid_version_header.rb | 2 + .../exceptions/invalid_versioner_option.rb | 2 + .../invalid_with_option_for_represent.rb | 2 + lib/grape/exceptions/method_not_allowed.rb | 2 + lib/grape/exceptions/missing_group_type.rb | 2 + lib/grape/exceptions/missing_mime_type.rb | 2 + lib/grape/exceptions/missing_option.rb | 2 + lib/grape/exceptions/missing_vendor_option.rb | 2 + lib/grape/exceptions/unknown_options.rb | 2 + lib/grape/exceptions/unknown_parameter.rb | 2 + lib/grape/exceptions/unknown_validator.rb | 2 + .../exceptions/unsupported_group_type.rb | 2 + lib/grape/exceptions/validation.rb | 2 + .../exceptions/validation_array_errors.rb | 2 + lib/grape/exceptions/validation_errors.rb | 2 + .../hash_with_indifferent_access.rb | 2 + lib/grape/extensions/deep_mergeable_hash.rb | 2 + lib/grape/extensions/deep_symbolize_hash.rb | 2 + lib/grape/extensions/hash.rb | 2 + lib/grape/extensions/hashie/mash.rb | 2 + lib/grape/formatter.rb | 2 + lib/grape/formatter/json.rb | 2 + lib/grape/formatter/serializable_hash.rb | 2 + lib/grape/formatter/txt.rb | 2 + lib/grape/formatter/xml.rb | 2 + lib/grape/http/headers.rb | 36 ++++++------ lib/grape/middleware/auth/base.rb | 2 + lib/grape/middleware/auth/dsl.rb | 2 + lib/grape/middleware/auth/strategies.rb | 2 + lib/grape/middleware/auth/strategy_info.rb | 2 + lib/grape/middleware/base.rb | 4 +- lib/grape/middleware/error.rb | 2 + lib/grape/middleware/filter.rb | 2 + lib/grape/middleware/formatter.rb | 4 +- lib/grape/middleware/globals.rb | 2 + lib/grape/middleware/helpers.rb | 2 + lib/grape/middleware/stack.rb | 2 + lib/grape/middleware/versioner.rb | 2 + .../versioner/accept_version_header.rb | 2 + lib/grape/middleware/versioner/header.rb | 2 + lib/grape/middleware/versioner/param.rb | 4 +- .../versioner/parse_media_type_patch.rb | 2 + lib/grape/middleware/versioner/path.rb | 2 + lib/grape/namespace.rb | 2 + lib/grape/parser.rb | 2 + lib/grape/parser/json.rb | 2 + lib/grape/parser/xml.rb | 2 + lib/grape/path.rb | 2 + lib/grape/presenters/presenter.rb | 2 + lib/grape/request.rb | 4 +- lib/grape/router.rb | 4 +- lib/grape/router/attribute_translator.rb | 2 + lib/grape/router/pattern.rb | 5 +- lib/grape/router/route.rb | 2 + lib/grape/serve_file/file_body.rb | 2 + lib/grape/serve_file/file_response.rb | 2 + lib/grape/serve_file/sendfile_response.rb | 2 + lib/grape/util/base_inheritable.rb | 2 + lib/grape/util/content_types.rb | 2 + lib/grape/util/endpoint_configuration.rb | 2 + lib/grape/util/env.rb | 36 ++++++------ lib/grape/util/inheritable_setting.rb | 2 + lib/grape/util/inheritable_values.rb | 2 + lib/grape/util/json.rb | 2 + lib/grape/util/lazy_block.rb | 2 + lib/grape/util/lazy_value.rb | 2 + lib/grape/util/registrable.rb | 2 + lib/grape/util/reverse_stackable_values.rb | 2 + lib/grape/util/stackable_values.rb | 2 + lib/grape/util/strict_hash_configuration.rb | 2 + lib/grape/util/xml.rb | 2 + lib/grape/validations.rb | 2 + lib/grape/validations/attributes_iterator.rb | 2 + .../multiple_attributes_iterator.rb | 2 + lib/grape/validations/params_scope.rb | 2 + .../validations/single_attribute_iterator.rb | 2 + lib/grape/validations/types.rb | 2 + lib/grape/validations/types/array_coercer.rb | 2 + lib/grape/validations/types/build_coercer.rb | 2 + .../validations/types/custom_type_coercer.rb | 2 + .../types/custom_type_collection_coercer.rb | 2 + .../validations/types/dry_type_coercer.rb | 2 + lib/grape/validations/types/file.rb | 2 + lib/grape/validations/types/json.rb | 2 + .../types/multiple_type_coercer.rb | 2 + .../validations/types/primitive_coercer.rb | 2 + lib/grape/validations/types/set_coercer.rb | 2 + .../types/variant_collection_coercer.rb | 2 + lib/grape/validations/validator_factory.rb | 2 + .../validations/validators/all_or_none.rb | 2 + .../validations/validators/allow_blank.rb | 2 + lib/grape/validations/validators/as.rb | 2 + .../validations/validators/at_least_one_of.rb | 2 + lib/grape/validations/validators/base.rb | 2 + lib/grape/validations/validators/coerce.rb | 2 + lib/grape/validations/validators/default.rb | 2 + .../validations/validators/exactly_one_of.rb | 2 + .../validations/validators/except_values.rb | 2 + .../validators/multiple_params_base.rb | 2 + .../validators/mutual_exclusion.rb | 2 + lib/grape/validations/validators/presence.rb | 2 + lib/grape/validations/validators/regexp.rb | 2 + lib/grape/validations/validators/same_as.rb | 2 + lib/grape/validations/validators/values.rb | 2 + lib/grape/version.rb | 4 +- spec/grape/api/custom_validations_spec.rb | 2 + .../grape/api/deeply_included_options_spec.rb | 2 + .../api/defines_boolean_in_params_spec.rb | 2 + spec/grape/api/inherited_helpers_spec.rb | 2 + spec/grape/api/invalid_format_spec.rb | 2 + .../api/namespace_parameters_in_route_spec.rb | 2 + spec/grape/api/nested_helpers_spec.rb | 2 + .../api/optional_parameters_in_route_spec.rb | 2 + .../grape/api/parameters_modification_spec.rb | 4 +- spec/grape/api/patch_method_helpers_spec.rb | 2 + spec/grape/api/recognize_path_spec.rb | 2 + .../api/required_parameters_in_route_spec.rb | 2 + ...red_parameters_with_invalid_method_spec.rb | 2 + .../api/routes_with_requirements_spec.rb | 2 + .../api/shared_helpers_exactly_one_of_spec.rb | 2 + spec/grape/api/shared_helpers_spec.rb | 2 + spec/grape/api_remount_spec.rb | 2 + spec/grape/api_spec.rb | 2 + spec/grape/config_spec.rb | 2 + spec/grape/dsl/callbacks_spec.rb | 2 + spec/grape/dsl/configuration_spec.rb | 2 + spec/grape/dsl/desc_spec.rb | 2 + spec/grape/dsl/headers_spec.rb | 2 + spec/grape/dsl/helpers_spec.rb | 2 + spec/grape/dsl/inside_route_spec.rb | 2 + spec/grape/dsl/logger_spec.rb | 2 + spec/grape/dsl/middleware_spec.rb | 2 + spec/grape/dsl/parameters_spec.rb | 2 + spec/grape/dsl/request_response_spec.rb | 2 + spec/grape/dsl/routing_spec.rb | 2 + spec/grape/dsl/settings_spec.rb | 2 + spec/grape/dsl/validations_spec.rb | 2 + spec/grape/endpoint_spec.rb | 2 + spec/grape/entity_spec.rb | 2 + spec/grape/exceptions/base_spec.rb | 2 + .../exceptions/body_parse_errors_spec.rb | 2 + .../exceptions/invalid_accept_header_spec.rb | 2 + .../exceptions/invalid_formatter_spec.rb | 2 + .../grape/exceptions/invalid_response_spec.rb | 2 + .../invalid_versioner_option_spec.rb | 2 + .../exceptions/missing_mime_type_spec.rb | 2 + spec/grape/exceptions/missing_option_spec.rb | 2 + spec/grape/exceptions/unknown_options_spec.rb | 2 + .../exceptions/unknown_validator_spec.rb | 2 + .../exceptions/validation_errors_spec.rb | 2 + spec/grape/exceptions/validation_spec.rb | 2 + .../extensions/param_builders/hash_spec.rb | 2 + .../hash_with_indifferent_access_spec.rb | 2 + .../param_builders/hashie/mash_spec.rb | 2 + .../global_namespace_function_spec.rb | 2 + spec/grape/integration/rack_sendfile_spec.rb | 2 + spec/grape/integration/rack_spec.rb | 2 + spec/grape/loading_spec.rb | 2 + spec/grape/middleware/auth/base_spec.rb | 2 + spec/grape/middleware/auth/dsl_spec.rb | 2 + spec/grape/middleware/auth/strategies_spec.rb | 2 + spec/grape/middleware/base_spec.rb | 2 + spec/grape/middleware/error_spec.rb | 2 + spec/grape/middleware/exception_spec.rb | 2 + spec/grape/middleware/formatter_spec.rb | 4 +- spec/grape/middleware/globals_spec.rb | 2 + spec/grape/middleware/stack_spec.rb | 2 + .../versioner/accept_version_header_spec.rb | 2 + .../grape/middleware/versioner/header_spec.rb | 2 + spec/grape/middleware/versioner/param_spec.rb | 2 + spec/grape/middleware/versioner/path_spec.rb | 2 + spec/grape/middleware/versioner_spec.rb | 2 + spec/grape/named_api_spec.rb | 2 + spec/grape/parser_spec.rb | 2 + spec/grape/path_spec.rb | 2 + spec/grape/presenters/presenter_spec.rb | 2 + spec/grape/request_spec.rb | 2 + spec/grape/util/inheritable_setting_spec.rb | 2 + spec/grape/util/inheritable_values_spec.rb | 2 + .../util/reverse_stackable_values_spec.rb | 2 + spec/grape/util/stackable_values_spec.rb | 2 + .../util/strict_hash_configuration_spec.rb | 2 + .../validations/attributes_iterator_spec.rb | 2 + .../validations/instance_behaivour_spec.rb | 2 + .../multiple_attributes_iterator_spec.rb | 2 + spec/grape/validations/params_scope_spec.rb | 2 + .../single_attribute_iterator_spec.rb | 2 + spec/grape/validations/types_spec.rb | 2 + .../validators/all_or_none_spec.rb | 2 + .../validators/allow_blank_spec.rb | 2 + .../validators/at_least_one_of_spec.rb | 2 + .../validations/validators/coerce_spec.rb | 2 + .../validations/validators/default_spec.rb | 2 + .../validators/exactly_one_of_spec.rb | 2 + .../validators/except_values_spec.rb | 2 + .../validators/mutual_exclusion_spec.rb | 2 + .../validations/validators/presence_spec.rb | 2 + .../validations/validators/regexp_spec.rb | 2 + .../validations/validators/same_as_spec.rb | 2 + .../validations/validators/values_spec.rb | 2 + spec/grape/validations_spec.rb | 2 + spec/integration/multi_json/json_spec.rb | 2 + spec/integration/multi_xml/xml_spec.rb | 2 + spec/shared/versioning_examples.rb | 2 + spec/spec_helper.rb | 2 + spec/support/basic_auth_encode_helpers.rb | 2 + spec/support/content_type_helpers.rb | 2 + spec/support/endpoint_faker.rb | 2 + spec/support/file_streamer.rb | 2 + spec/support/integer_helpers.rb | 2 + spec/support/versioned_helpers.rb | 2 + 261 files changed, 612 insertions(+), 56 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 9e43b4527..995d9ed9d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ AllCops: - TargetRubyVersion: 2.1 + TargetRubyVersion: 2.4 Include: - Dangerfile - gemfiles/*.gemfile diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index cf7b6377e..627a6dca3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-12-20 21:38:24 +0000 using RuboCop version 0.51.0. +# on 2019-12-09 18:07:42 +0100 using RuboCop version 0.51.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -26,41 +26,47 @@ Lint/RescueWithoutErrorClass: Exclude: - 'lib/grape/validations/validators/coerce.rb' -# Offense count: 47 +# Offense count: 1 +# Cop supports --auto-correct. +Lint/UnneededRequireStatement: + Exclude: + - 'lib/grape.rb' + +# Offense count: 46 Metrics/AbcSize: Max: 44 -# Offense count: 4 +# Offense count: 7 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: Max: 182 -# Offense count: 9 +# Offense count: 10 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 302 -# Offense count: 31 +# Offense count: 30 Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 1234 +# Offense count: 1300 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 215 -# Offense count: 57 +# Offense count: 60 # Configuration parameters: CountComments. Metrics/MethodLength: - Max: 34 + Max: 33 # Offense count: 12 # Configuration parameters: CountComments. Metrics/ModuleLength: Max: 220 -# Offense count: 21 +# Offense count: 22 Metrics/PerceivedComplexity: Max: 14 @@ -74,8 +80,40 @@ Naming/FileName: - 'Guardfile' - 'Rakefile' +# Offense count: 1 +# Cop supports --auto-correct. +Performance/RegexpMatch: + Exclude: + - 'lib/grape/middleware/versioner/path.rb' + # Offense count: 2 # Configuration parameters: SupportedStyles. # SupportedStyles: annotated, template Style/FormatStringToken: EnforcedStyle: template + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'lib/grape/middleware/formatter.rb' + +# Offense count: 12 +# Cop supports --auto-correct. +# Configuration parameters: ConvertCodeThatCanStartToReturnNil. +Style/SafeNavigation: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/dsl/desc.rb' + - 'lib/grape/dsl/helpers.rb' + - 'lib/grape/dsl/inside_route.rb' + - 'lib/grape/dsl/routing.rb' + - 'lib/grape/endpoint.rb' + - 'lib/grape/middleware/error.rb' + - 'lib/grape/middleware/versioner/accept_version_header.rb' + - 'lib/grape/middleware/versioner/header.rb' + - 'lib/grape/middleware/versioner/param.rb' + - 'lib/grape/middleware/versioner/path.rb' diff --git a/Appraisals b/Appraisals index 1cb41c12b..a779aab25 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,5 @@ +# frozen_string_literal: true + appraise 'rails-5' do gem 'rails', '5.2.1' end diff --git a/CHANGELOG.md b/CHANGELOG.md index 3948d6510..e5afb0324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1941](https://github.com/ruby-grape/grape/pull/1941): Frozen string literal - [@ericproulx](https://github.com/ericproulx). * [#1940](https://github.com/ruby-grape/grape/pull/1940): Get rid of a needless step in HashWithIndifferentAccess - [@dnesteryuk](https://github.com/dnesteryuk). * [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien). * [#1920](https://github.com/ruby-grape/grape/pull/1920): Replace Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/Dangerfile b/Dangerfile index 5ff5d8e71..2f1200640 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,2 +1,4 @@ +# frozen_string_literal: true + danger.import_dangerfile(gem: 'ruby-grape-danger') toc.check diff --git a/Gemfile b/Gemfile index b088fc793..0469596f9 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # when changing this file, run appraisal install ; rubocop -a gemfiles/*.gemfile source 'https://rubygems.org' diff --git a/Guardfile b/Guardfile index ad1b72546..f771d0349 100644 --- a/Guardfile +++ b/Guardfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + guard :rspec, all_on_start: true, cmd: 'bundle exec rspec' do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } diff --git a/Rakefile b/Rakefile index 2beb99875..58788aa82 100644 --- a/Rakefile +++ b/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rubygems' require 'bundler' Bundler.setup :default, :test, :development diff --git a/benchmark/nested_params.rb b/benchmark/nested_params.rb index 1e4f88133..2523939b8 100644 --- a/benchmark/nested_params.rb +++ b/benchmark/nested_params.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape' require 'benchmark/ips' diff --git a/benchmark/simple.rb b/benchmark/simple.rb index 022cebe8b..053c33351 100644 --- a/benchmark/simple.rb +++ b/benchmark/simple.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'grape' require 'benchmark/ips' diff --git a/benchmark/simple_with_type_coercer.rb b/benchmark/simple_with_type_coercer.rb index 2a1c408d3..1dc0e7be4 100644 --- a/benchmark/simple_with_type_coercer.rb +++ b/benchmark/simple_with_type_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'grape' require 'benchmark/ips' diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index a8b1573a2..a76b25ac2 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source 'https://rubygems.org' diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index 4ade375c4..0c520d1f7 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source 'https://rubygems.org' diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index 4065843b5..3e4eddf8f 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source 'https://rubygems.org' diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 80f19f16a..c05f03ee8 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source 'https://rubygems.org' diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index 369f50e80..3713d04fc 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file was generated by Appraisal source 'https://rubygems.org' diff --git a/grape.gemspec b/grape.gemspec index 9062ae7ea..3a779d3fb 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) require 'grape/version' diff --git a/lib/grape.rb b/lib/grape.rb index 048ea2e66..388fb54bd 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'logger' require 'rack' require 'rack/builder' diff --git a/lib/grape/api.rb b/lib/grape/api.rb index ac9a7513a..7df189d35 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/router' require 'grape/api/instance' diff --git a/lib/grape/api/helpers.rb b/lib/grape/api/helpers.rb index 7fd69e7db..00da38f86 100644 --- a/lib/grape/api/helpers.rb +++ b/lib/grape/api/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape class API module Helpers diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 5d8ac7cd4..8efa5568a 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/router' module Grape diff --git a/lib/grape/config.rb b/lib/grape/config.rb index a1ebb706a..6d76c06b1 100644 --- a/lib/grape/config.rb +++ b/lib/grape/config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Config class Configuration diff --git a/lib/grape/cookies.rb b/lib/grape/cookies.rb index 48a8389bc..6e37c6d32 100644 --- a/lib/grape/cookies.rb +++ b/lib/grape/cookies.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape class Cookies def initialize diff --git a/lib/grape/dsl/api.rb b/lib/grape/dsl/api.rb index 1883170e9..0543cac54 100644 --- a/lib/grape/dsl/api.rb +++ b/lib/grape/dsl/api.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/callbacks.rb b/lib/grape/dsl/callbacks.rb index a74144995..ae6049aa2 100644 --- a/lib/grape/dsl/callbacks.rb +++ b/lib/grape/dsl/callbacks.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/configuration.rb b/lib/grape/dsl/configuration.rb index 3ee20f062..f33af3225 100644 --- a/lib/grape/dsl/configuration.rb +++ b/lib/grape/dsl/configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/desc.rb b/lib/grape/dsl/desc.rb index e8122b544..8e94750c7 100644 --- a/lib/grape/dsl/desc.rb +++ b/lib/grape/dsl/desc.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module DSL module Desc diff --git a/lib/grape/dsl/headers.rb b/lib/grape/dsl/headers.rb index cf7849352..c3c7bc3e8 100644 --- a/lib/grape/dsl/headers.rb +++ b/lib/grape/dsl/headers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module DSL module Headers diff --git a/lib/grape/dsl/helpers.rb b/lib/grape/dsl/helpers.rb index 718511799..0d3c7e481 100644 --- a/lib/grape/dsl/helpers.rb +++ b/lib/grape/dsl/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 0fc5948ad..57fbc8b94 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' require 'grape/dsl/headers' diff --git a/lib/grape/dsl/logger.rb b/lib/grape/dsl/logger.rb index 40cccdaac..516cdcd7d 100644 --- a/lib/grape/dsl/logger.rb +++ b/lib/grape/dsl/logger.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module DSL module Logger diff --git a/lib/grape/dsl/middleware.rb b/lib/grape/dsl/middleware.rb index bd30c7498..f07f310b1 100644 --- a/lib/grape/dsl/middleware.rb +++ b/lib/grape/dsl/middleware.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index 42e80043c..7fa0e6307 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/request_response.rb b/lib/grape/dsl/request_response.rb index 654a31cdb..02587e346 100644 --- a/lib/grape/dsl/request_response.rb +++ b/lib/grape/dsl/request_response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index bf5f4c65a..e89b9ee34 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/settings.rb b/lib/grape/dsl/settings.rb index 9fe24f83a..eebd12750 100644 --- a/lib/grape/dsl/settings.rb +++ b/lib/grape/dsl/settings.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/dsl/validations.rb b/lib/grape/dsl/validations.rb index 91f98899c..a426a11aa 100644 --- a/lib/grape/dsl/validations.rb +++ b/lib/grape/dsl/validations.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'active_support/concern' module Grape diff --git a/lib/grape/eager_load.rb b/lib/grape/eager_load.rb index 933c97908..91baa9e57 100644 --- a/lib/grape/eager_load.rb +++ b/lib/grape/eager_load.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Grape.eager_load! Grape::Http.eager_load! Grape::Exceptions.eager_load! diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 336db40b1..b6085973f 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape # An Endpoint is the proxy scope in which all routing # blocks are executed. In other words, any methods diff --git a/lib/grape/error_formatter.rb b/lib/grape/error_formatter.rb index 85107f6e1..535038492 100644 --- a/lib/grape/error_formatter.rb +++ b/lib/grape/error_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ErrorFormatter extend Util::Registrable diff --git a/lib/grape/error_formatter/base.rb b/lib/grape/error_formatter/base.rb index 60103a600..f0c802a45 100644 --- a/lib/grape/error_formatter/base.rb +++ b/lib/grape/error_formatter/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ErrorFormatter module Base diff --git a/lib/grape/error_formatter/json.rb b/lib/grape/error_formatter/json.rb index d3ebff765..6c160e099 100644 --- a/lib/grape/error_formatter/json.rb +++ b/lib/grape/error_formatter/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ErrorFormatter module Json diff --git a/lib/grape/error_formatter/txt.rb b/lib/grape/error_formatter/txt.rb index b58e03152..76d3cb3f1 100644 --- a/lib/grape/error_formatter/txt.rb +++ b/lib/grape/error_formatter/txt.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ErrorFormatter module Txt diff --git a/lib/grape/error_formatter/xml.rb b/lib/grape/error_formatter/xml.rb index 73ebf09c8..39ea87387 100644 --- a/lib/grape/error_formatter/xml.rb +++ b/lib/grape/error_formatter/xml.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ErrorFormatter module Xml diff --git a/lib/grape/exceptions/base.rb b/lib/grape/exceptions/base.rb index 82ddbae69..25d3f08c3 100644 --- a/lib/grape/exceptions/base.rb +++ b/lib/grape/exceptions/base.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + module Grape module Exceptions class Base < StandardError - BASE_MESSAGES_KEY = 'grape.errors.messages'.freeze - BASE_ATTRIBUTES_KEY = 'grape.errors.attributes'.freeze + BASE_MESSAGES_KEY = 'grape.errors.messages' + BASE_ATTRIBUTES_KEY = 'grape.errors.attributes' FALLBACK_LOCALE = :en attr_reader :status, :message, :headers @@ -28,7 +30,7 @@ def compose_message(key, **attributes) @problem = problem(key, **attributes) @summary = summary(key, **attributes) @resolution = resolution(key, **attributes) - [['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].reduce('') do |message, detail_array| + [['Problem', @problem], ['Summary', @summary], ['Resolution', @resolution]].each_with_object(+'') do |detail_array, message| message << "\n#{detail_array[0]}:\n #{detail_array[1]}" unless detail_array[1].blank? message end diff --git a/lib/grape/exceptions/incompatible_option_values.rb b/lib/grape/exceptions/incompatible_option_values.rb index 971bdeab1..aabe8d5eb 100644 --- a/lib/grape/exceptions/incompatible_option_values.rb +++ b/lib/grape/exceptions/incompatible_option_values.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class IncompatibleOptionValues < Base diff --git a/lib/grape/exceptions/invalid_accept_header.rb b/lib/grape/exceptions/invalid_accept_header.rb index faafbb439..fd98849c0 100644 --- a/lib/grape/exceptions/invalid_accept_header.rb +++ b/lib/grape/exceptions/invalid_accept_header.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidAcceptHeader < Base diff --git a/lib/grape/exceptions/invalid_formatter.rb b/lib/grape/exceptions/invalid_formatter.rb index d3e5ac9e0..8c8a82f09 100644 --- a/lib/grape/exceptions/invalid_formatter.rb +++ b/lib/grape/exceptions/invalid_formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidFormatter < Base diff --git a/lib/grape/exceptions/invalid_message_body.rb b/lib/grape/exceptions/invalid_message_body.rb index 9e4b6eec3..ac9c2efbf 100644 --- a/lib/grape/exceptions/invalid_message_body.rb +++ b/lib/grape/exceptions/invalid_message_body.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidMessageBody < Base diff --git a/lib/grape/exceptions/invalid_response.rb b/lib/grape/exceptions/invalid_response.rb index 3b92ab9cc..197f8399f 100644 --- a/lib/grape/exceptions/invalid_response.rb +++ b/lib/grape/exceptions/invalid_response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidResponse < Base diff --git a/lib/grape/exceptions/invalid_version_header.rb b/lib/grape/exceptions/invalid_version_header.rb index b9273bb46..cb01ec3d1 100644 --- a/lib/grape/exceptions/invalid_version_header.rb +++ b/lib/grape/exceptions/invalid_version_header.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidVersionHeader < Base diff --git a/lib/grape/exceptions/invalid_versioner_option.rb b/lib/grape/exceptions/invalid_versioner_option.rb index ea3ca0755..9411370b0 100644 --- a/lib/grape/exceptions/invalid_versioner_option.rb +++ b/lib/grape/exceptions/invalid_versioner_option.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidVersionerOption < Base diff --git a/lib/grape/exceptions/invalid_with_option_for_represent.rb b/lib/grape/exceptions/invalid_with_option_for_represent.rb index 04d380fb3..d6a275fb9 100644 --- a/lib/grape/exceptions/invalid_with_option_for_represent.rb +++ b/lib/grape/exceptions/invalid_with_option_for_represent.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class InvalidWithOptionForRepresent < Base diff --git a/lib/grape/exceptions/method_not_allowed.rb b/lib/grape/exceptions/method_not_allowed.rb index 91fedd611..5777e4c29 100644 --- a/lib/grape/exceptions/method_not_allowed.rb +++ b/lib/grape/exceptions/method_not_allowed.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class MethodNotAllowed < Base diff --git a/lib/grape/exceptions/missing_group_type.rb b/lib/grape/exceptions/missing_group_type.rb index 717e2412e..398113ff8 100644 --- a/lib/grape/exceptions/missing_group_type.rb +++ b/lib/grape/exceptions/missing_group_type.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class MissingGroupTypeError < Base diff --git a/lib/grape/exceptions/missing_mime_type.rb b/lib/grape/exceptions/missing_mime_type.rb index 3fc296def..5bf43a1dc 100644 --- a/lib/grape/exceptions/missing_mime_type.rb +++ b/lib/grape/exceptions/missing_mime_type.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class MissingMimeType < Base diff --git a/lib/grape/exceptions/missing_option.rb b/lib/grape/exceptions/missing_option.rb index e37e4c368..ec35b96e8 100644 --- a/lib/grape/exceptions/missing_option.rb +++ b/lib/grape/exceptions/missing_option.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class MissingOption < Base diff --git a/lib/grape/exceptions/missing_vendor_option.rb b/lib/grape/exceptions/missing_vendor_option.rb index ae5750fec..46e04885f 100644 --- a/lib/grape/exceptions/missing_vendor_option.rb +++ b/lib/grape/exceptions/missing_vendor_option.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class MissingVendorOption < Base diff --git a/lib/grape/exceptions/unknown_options.rb b/lib/grape/exceptions/unknown_options.rb index 59937f52b..0512a8778 100644 --- a/lib/grape/exceptions/unknown_options.rb +++ b/lib/grape/exceptions/unknown_options.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class UnknownOptions < Base diff --git a/lib/grape/exceptions/unknown_parameter.rb b/lib/grape/exceptions/unknown_parameter.rb index 76fada189..0168ff39d 100644 --- a/lib/grape/exceptions/unknown_parameter.rb +++ b/lib/grape/exceptions/unknown_parameter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class UnknownParameter < Base diff --git a/lib/grape/exceptions/unknown_validator.rb b/lib/grape/exceptions/unknown_validator.rb index ddebcde86..e856f2b36 100644 --- a/lib/grape/exceptions/unknown_validator.rb +++ b/lib/grape/exceptions/unknown_validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class UnknownValidator < Base diff --git a/lib/grape/exceptions/unsupported_group_type.rb b/lib/grape/exceptions/unsupported_group_type.rb index 9602e7331..9cbc7aac2 100644 --- a/lib/grape/exceptions/unsupported_group_type.rb +++ b/lib/grape/exceptions/unsupported_group_type.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class UnsupportedGroupTypeError < Base diff --git a/lib/grape/exceptions/validation.rb b/lib/grape/exceptions/validation.rb index c4aba6a15..8b519d9c2 100644 --- a/lib/grape/exceptions/validation.rb +++ b/lib/grape/exceptions/validation.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/exceptions/base' module Grape diff --git a/lib/grape/exceptions/validation_array_errors.rb b/lib/grape/exceptions/validation_array_errors.rb index 9e672b90a..d596c8877 100644 --- a/lib/grape/exceptions/validation_array_errors.rb +++ b/lib/grape/exceptions/validation_array_errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Exceptions class ValidationArrayErrors < Base diff --git a/lib/grape/exceptions/validation_errors.rb b/lib/grape/exceptions/validation_errors.rb index 822a5fce4..8123e0847 100644 --- a/lib/grape/exceptions/validation_errors.rb +++ b/lib/grape/exceptions/validation_errors.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/exceptions/base' module Grape diff --git a/lib/grape/extensions/active_support/hash_with_indifferent_access.rb b/lib/grape/extensions/active_support/hash_with_indifferent_access.rb index 105e236f5..d412c91dd 100644 --- a/lib/grape/extensions/active_support/hash_with_indifferent_access.rb +++ b/lib/grape/extensions/active_support/hash_with_indifferent_access.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Extensions module ActiveSupport diff --git a/lib/grape/extensions/deep_mergeable_hash.rb b/lib/grape/extensions/deep_mergeable_hash.rb index 8f9903d62..825bbe892 100644 --- a/lib/grape/extensions/deep_mergeable_hash.rb +++ b/lib/grape/extensions/deep_mergeable_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Extensions class DeepMergeableHash < ::Hash diff --git a/lib/grape/extensions/deep_symbolize_hash.rb b/lib/grape/extensions/deep_symbolize_hash.rb index 310a658af..6c131a97b 100644 --- a/lib/grape/extensions/deep_symbolize_hash.rb +++ b/lib/grape/extensions/deep_symbolize_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Extensions module DeepSymbolizeHash diff --git a/lib/grape/extensions/hash.rb b/lib/grape/extensions/hash.rb index 77d075763..990dddfbc 100644 --- a/lib/grape/extensions/hash.rb +++ b/lib/grape/extensions/hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Extensions module Hash diff --git a/lib/grape/extensions/hashie/mash.rb b/lib/grape/extensions/hashie/mash.rb index 6afa106d9..545e71952 100644 --- a/lib/grape/extensions/hashie/mash.rb +++ b/lib/grape/extensions/hashie/mash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Extensions module Hashie diff --git a/lib/grape/formatter.rb b/lib/grape/formatter.rb index ef95fcb7a..a9cebaef2 100644 --- a/lib/grape/formatter.rb +++ b/lib/grape/formatter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Formatter extend Util::Registrable diff --git a/lib/grape/formatter/json.rb b/lib/grape/formatter/json.rb index 5f96918b2..753468a3c 100644 --- a/lib/grape/formatter/json.rb +++ b/lib/grape/formatter/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Formatter module Json diff --git a/lib/grape/formatter/serializable_hash.rb b/lib/grape/formatter/serializable_hash.rb index c31fcebb0..5b6256d59 100644 --- a/lib/grape/formatter/serializable_hash.rb +++ b/lib/grape/formatter/serializable_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Formatter module SerializableHash diff --git a/lib/grape/formatter/txt.rb b/lib/grape/formatter/txt.rb index 175944ede..1f1f9ef2f 100644 --- a/lib/grape/formatter/txt.rb +++ b/lib/grape/formatter/txt.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Formatter module Txt diff --git a/lib/grape/formatter/xml.rb b/lib/grape/formatter/xml.rb index 2e6853920..9db91d015 100644 --- a/lib/grape/formatter/xml.rb +++ b/lib/grape/formatter/xml.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Formatter module Xml diff --git a/lib/grape/http/headers.rb b/lib/grape/http/headers.rb index c1e587470..ddecc2b8b 100644 --- a/lib/grape/http/headers.rb +++ b/lib/grape/http/headers.rb @@ -1,29 +1,31 @@ +# frozen_string_literal: true + module Grape module Http module Headers # https://github.com/rack/rack/blob/master/lib/rack.rb - HTTP_VERSION = 'HTTP_VERSION'.freeze - PATH_INFO = 'PATH_INFO'.freeze - REQUEST_METHOD = 'REQUEST_METHOD'.freeze - QUERY_STRING = 'QUERY_STRING'.freeze - CONTENT_TYPE = 'Content-Type'.freeze + HTTP_VERSION = 'HTTP_VERSION' + PATH_INFO = 'PATH_INFO' + REQUEST_METHOD = 'REQUEST_METHOD' + QUERY_STRING = 'QUERY_STRING' + CONTENT_TYPE = 'Content-Type' - GET = 'GET'.freeze - POST = 'POST'.freeze - PUT = 'PUT'.freeze - PATCH = 'PATCH'.freeze - DELETE = 'DELETE'.freeze - HEAD = 'HEAD'.freeze - OPTIONS = 'OPTIONS'.freeze + GET = 'GET' + POST = 'POST' + PUT = 'PUT' + PATCH = 'PATCH' + DELETE = 'DELETE' + HEAD = 'HEAD' + OPTIONS = 'OPTIONS' SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze - HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION'.freeze - X_CASCADE = 'X-Cascade'.freeze - HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING'.freeze - HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze + HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION' + X_CASCADE = 'X-Cascade' + HTTP_TRANSFER_ENCODING = 'HTTP_TRANSFER_ENCODING' + HTTP_ACCEPT = 'HTTP_ACCEPT' - FORMAT = 'format'.freeze + FORMAT = 'format' end end end diff --git a/lib/grape/middleware/auth/base.rb b/lib/grape/middleware/auth/base.rb index a3d34b131..61a8cc588 100644 --- a/lib/grape/middleware/auth/base.rb +++ b/lib/grape/middleware/auth/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/auth/basic' module Grape diff --git a/lib/grape/middleware/auth/dsl.rb b/lib/grape/middleware/auth/dsl.rb index 49462d847..1b2e8f456 100644 --- a/lib/grape/middleware/auth/dsl.rb +++ b/lib/grape/middleware/auth/dsl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/auth/basic' require 'active_support/concern' diff --git a/lib/grape/middleware/auth/strategies.rb b/lib/grape/middleware/auth/strategies.rb index 90375b793..dc36eea48 100644 --- a/lib/grape/middleware/auth/strategies.rb +++ b/lib/grape/middleware/auth/strategies.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Middleware module Auth diff --git a/lib/grape/middleware/auth/strategy_info.rb b/lib/grape/middleware/auth/strategy_info.rb index e04656b76..20eb25b7d 100644 --- a/lib/grape/middleware/auth/strategy_info.rb +++ b/lib/grape/middleware/auth/strategy_info.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Middleware module Auth diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 945225a57..bc96feebf 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/dsl/headers' module Grape @@ -6,7 +8,7 @@ class Base include Helpers attr_reader :app, :env, :options - TEXT_HTML = 'text/html'.freeze + TEXT_HTML = 'text/html' include Grape::DSL::Headers diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index e7d6da7e3..d6591964d 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/middleware/base' require 'active_support/core_ext/string/output_safety' diff --git a/lib/grape/middleware/filter.rb b/lib/grape/middleware/filter.rb index 2b52701d3..10d044691 100644 --- a/lib/grape/middleware/filter.rb +++ b/lib/grape/middleware/filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Middleware # This is a simple middleware for adding before and after filters diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 4f5fccc2e..3667451cd 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + require 'grape/middleware/base' module Grape module Middleware class Formatter < Base - CHUNKED = 'chunked'.freeze + CHUNKED = 'chunked' def default_options { diff --git a/lib/grape/middleware/globals.rb b/lib/grape/middleware/globals.rb index f356030e3..850f24196 100644 --- a/lib/grape/middleware/globals.rb +++ b/lib/grape/middleware/globals.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/middleware/base' module Grape diff --git a/lib/grape/middleware/helpers.rb b/lib/grape/middleware/helpers.rb index eef648ed6..013a9587d 100644 --- a/lib/grape/middleware/helpers.rb +++ b/lib/grape/middleware/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Middleware # Common methods for all types of Grape middleware diff --git a/lib/grape/middleware/stack.rb b/lib/grape/middleware/stack.rb index abfbdaa45..8509a4653 100644 --- a/lib/grape/middleware/stack.rb +++ b/lib/grape/middleware/stack.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Middleware # Class to handle the stack of middlewares based on ActionDispatch::MiddlewareStack diff --git a/lib/grape/middleware/versioner.rb b/lib/grape/middleware/versioner.rb index 82ba1ba74..d40c87a27 100644 --- a/lib/grape/middleware/versioner.rb +++ b/lib/grape/middleware/versioner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Versioners set env['api.version'] when a version is defined on an API and # on the requests. The current methods for determining version are: # diff --git a/lib/grape/middleware/versioner/accept_version_header.rb b/lib/grape/middleware/versioner/accept_version_header.rb index 46cd769c7..953a78392 100644 --- a/lib/grape/middleware/versioner/accept_version_header.rb +++ b/lib/grape/middleware/versioner/accept_version_header.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/middleware/base' module Grape diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index f90bc37f0..0d0bcf2da 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/middleware/base' require 'grape/middleware/versioner/parse_media_type_patch' diff --git a/lib/grape/middleware/versioner/param.rb b/lib/grape/middleware/versioner/param.rb index d51a148c1..8e7b17a4e 100644 --- a/lib/grape/middleware/versioner/param.rb +++ b/lib/grape/middleware/versioner/param.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/middleware/base' module Grape @@ -22,7 +24,7 @@ class Param < Base def default_options { version_options: { - parameter: 'apiver'.freeze + parameter: 'apiver' } } end diff --git a/lib/grape/middleware/versioner/parse_media_type_patch.rb b/lib/grape/middleware/versioner/parse_media_type_patch.rb index cf0c987ef..40c72b6bf 100644 --- a/lib/grape/middleware/versioner/parse_media_type_patch.rb +++ b/lib/grape/middleware/versioner/parse_media_type_patch.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Rack module Accept module Header diff --git a/lib/grape/middleware/versioner/path.rb b/lib/grape/middleware/versioner/path.rb index e57f0e199..80c3a1fd9 100644 --- a/lib/grape/middleware/versioner/path.rb +++ b/lib/grape/middleware/versioner/path.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/middleware/base' module Grape diff --git a/lib/grape/namespace.rb b/lib/grape/namespace.rb index 9b3696859..a15c460b6 100644 --- a/lib/grape/namespace.rb +++ b/lib/grape/namespace.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape # A container for endpoints or other namespaces, which allows for both # logical grouping of endpoints as well as sharing common configuration. diff --git a/lib/grape/parser.rb b/lib/grape/parser.rb index a1e87bb40..e7bf622fc 100644 --- a/lib/grape/parser.rb +++ b/lib/grape/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Parser extend Util::Registrable diff --git a/lib/grape/parser/json.rb b/lib/grape/parser/json.rb index 89b50d3f2..7f72c9a94 100644 --- a/lib/grape/parser/json.rb +++ b/lib/grape/parser/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Parser module Json diff --git a/lib/grape/parser/xml.rb b/lib/grape/parser/xml.rb index 9fadf1c64..20cde6e27 100644 --- a/lib/grape/parser/xml.rb +++ b/lib/grape/parser/xml.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Parser module Xml diff --git a/lib/grape/path.rb b/lib/grape/path.rb index 86f51f34c..6520d525f 100644 --- a/lib/grape/path.rb +++ b/lib/grape/path.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape # Represents a path to an endpoint. class Path diff --git a/lib/grape/presenters/presenter.rb b/lib/grape/presenters/presenter.rb index 0f653797b..78c812178 100644 --- a/lib/grape/presenters/presenter.rb +++ b/lib/grape/presenters/presenter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Presenters class Presenter diff --git a/lib/grape/request.rb b/lib/grape/request.rb index 7209a60d0..79c65f475 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + module Grape class Request < Rack::Request - HTTP_PREFIX = 'HTTP_'.freeze + HTTP_PREFIX = 'HTTP_' alias rack_params params diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 54bcfd1d3..249602839 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/router/route' module Grape @@ -12,7 +14,7 @@ def initialize(pattern, **attributes) end def self.normalize_path(path) - path = "/#{path}" + path = +"/#{path}" path.squeeze!('/') path.sub!(%r{/+\Z}, '') path = '/' if path == '' diff --git a/lib/grape/router/attribute_translator.rb b/lib/grape/router/attribute_translator.rb index 8614fa66e..aa66662ff 100644 --- a/lib/grape/router/attribute_translator.rb +++ b/lib/grape/router/attribute_translator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape class Router # this could be an OpenStruct, but doesn't work in Ruby 2.3.0, see https://bugs.ruby-lang.org/issues/12251 diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index 9f326ecc0..9da362f5c 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'forwardable' require 'mustermann/grape' @@ -36,6 +38,7 @@ def pattern_options def build_path(pattern, anchor: false, suffix: nil, **_options) unless anchor || pattern.end_with?('*path') + pattern = +pattern pattern << '/' unless pattern.end_with?('/') pattern << '*path' end @@ -44,7 +47,7 @@ def build_path(pattern, anchor: false, suffix: nil, **_options) parts[parts.length - 1] = '?' + parts.last end.join('/') if pattern.end_with?('*path') - pattern + suffix.to_s + "#{pattern}#{suffix}" end def extract_capture(requirements: {}, **options) diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 0eff8607f..18caf9488 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/router/pattern' require 'grape/router/attribute_translator' require 'forwardable' diff --git a/lib/grape/serve_file/file_body.rb b/lib/grape/serve_file/file_body.rb index ba9fa3a1e..da4dd9e3f 100644 --- a/lib/grape/serve_file/file_body.rb +++ b/lib/grape/serve_file/file_body.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ServeFile CHUNK_SIZE = 16_384 diff --git a/lib/grape/serve_file/file_response.rb b/lib/grape/serve_file/file_response.rb index 07b98cd21..88928d33c 100644 --- a/lib/grape/serve_file/file_response.rb +++ b/lib/grape/serve_file/file_response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ServeFile # A simple class used to identify responses which represent files and do not diff --git a/lib/grape/serve_file/sendfile_response.rb b/lib/grape/serve_file/sendfile_response.rb index 17312a644..5ff723b96 100644 --- a/lib/grape/serve_file/sendfile_response.rb +++ b/lib/grape/serve_file/sendfile_response.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ServeFile # Response should respond to to_path method diff --git a/lib/grape/util/base_inheritable.rb b/lib/grape/util/base_inheritable.rb index 277c61d5b..9af28b182 100644 --- a/lib/grape/util/base_inheritable.rb +++ b/lib/grape/util/base_inheritable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util # Base for classes which need to operate with own values kept diff --git a/lib/grape/util/content_types.rb b/lib/grape/util/content_types.rb index 8b2122402..df5a7b008 100644 --- a/lib/grape/util/content_types.rb +++ b/lib/grape/util/content_types.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module ContentTypes # Content types are listed in order of preference. diff --git a/lib/grape/util/endpoint_configuration.rb b/lib/grape/util/endpoint_configuration.rb index 27296a578..90ad256f8 100644 --- a/lib/grape/util/endpoint_configuration.rb +++ b/lib/grape/util/endpoint_configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util class EndpointConfiguration < LazyValueHash diff --git a/lib/grape/util/env.rb b/lib/grape/util/env.rb index eaa95f917..a6023bcc1 100644 --- a/lib/grape/util/env.rb +++ b/lib/grape/util/env.rb @@ -1,23 +1,25 @@ +# frozen_string_literal: true + module Grape module Env - API_VERSION = 'api.version'.freeze - API_ENDPOINT = 'api.endpoint'.freeze - API_REQUEST_INPUT = 'api.request.input'.freeze - API_REQUEST_BODY = 'api.request.body'.freeze - API_TYPE = 'api.type'.freeze - API_SUBTYPE = 'api.subtype'.freeze - API_VENDOR = 'api.vendor'.freeze - API_FORMAT = 'api.format'.freeze + API_VERSION = 'api.version' + API_ENDPOINT = 'api.endpoint' + API_REQUEST_INPUT = 'api.request.input' + API_REQUEST_BODY = 'api.request.body' + API_TYPE = 'api.type' + API_SUBTYPE = 'api.subtype' + API_VENDOR = 'api.vendor' + API_FORMAT = 'api.format' - RACK_INPUT = 'rack.input'.freeze - RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash'.freeze - RACK_REQUEST_FORM_HASH = 'rack.request.form_hash'.freeze - RACK_REQUEST_FORM_INPUT = 'rack.request.form_input'.freeze + RACK_INPUT = 'rack.input' + RACK_REQUEST_QUERY_HASH = 'rack.request.query_hash' + RACK_REQUEST_FORM_HASH = 'rack.request.form_hash' + RACK_REQUEST_FORM_INPUT = 'rack.request.form_input' - GRAPE_REQUEST = 'grape.request'.freeze - GRAPE_REQUEST_HEADERS = 'grape.request.headers'.freeze - GRAPE_REQUEST_PARAMS = 'grape.request.params'.freeze - GRAPE_ROUTING_ARGS = 'grape.routing_args'.freeze - GRAPE_ALLOWED_METHODS = 'grape.allowed_methods'.freeze + GRAPE_REQUEST = 'grape.request' + GRAPE_REQUEST_HEADERS = 'grape.request.headers' + GRAPE_REQUEST_PARAMS = 'grape.request.params' + GRAPE_ROUTING_ARGS = 'grape.routing_args' + GRAPE_ALLOWED_METHODS = 'grape.allowed_methods' end end diff --git a/lib/grape/util/inheritable_setting.rb b/lib/grape/util/inheritable_setting.rb index 2d3021861..2a6024bca 100644 --- a/lib/grape/util/inheritable_setting.rb +++ b/lib/grape/util/inheritable_setting.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util # A branchable, inheritable settings object which can store both stackable diff --git a/lib/grape/util/inheritable_values.rb b/lib/grape/util/inheritable_values.rb index bba7be02b..6b43f8ca2 100644 --- a/lib/grape/util/inheritable_values.rb +++ b/lib/grape/util/inheritable_values.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'base_inheritable' module Grape diff --git a/lib/grape/util/json.rb b/lib/grape/util/json.rb index b0f5addb0..9381d841a 100644 --- a/lib/grape/util/json.rb +++ b/lib/grape/util/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape if Object.const_defined? :MultiJson Json = ::MultiJson diff --git a/lib/grape/util/lazy_block.rb b/lib/grape/util/lazy_block.rb index 7fe842e19..6e7d18b8a 100644 --- a/lib/grape/util/lazy_block.rb +++ b/lib/grape/util/lazy_block.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util class LazyBlock diff --git a/lib/grape/util/lazy_value.rb b/lib/grape/util/lazy_value.rb index 0d01c66f0..b11364538 100644 --- a/lib/grape/util/lazy_value.rb +++ b/lib/grape/util/lazy_value.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util class LazyValue diff --git a/lib/grape/util/registrable.rb b/lib/grape/util/registrable.rb index 66733a57d..e154456f8 100644 --- a/lib/grape/util/registrable.rb +++ b/lib/grape/util/registrable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util module Registrable diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb index 8858c83fe..d8e8f160c 100644 --- a/lib/grape/util/reverse_stackable_values.rb +++ b/lib/grape/util/reverse_stackable_values.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'stackable_values' module Grape diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 4c97adace..324aa19db 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'base_inheritable' module Grape diff --git a/lib/grape/util/strict_hash_configuration.rb b/lib/grape/util/strict_hash_configuration.rb index 73cdb9252..91fa41399 100644 --- a/lib/grape/util/strict_hash_configuration.rb +++ b/lib/grape/util/strict_hash_configuration.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Util module StrictHashConfiguration diff --git a/lib/grape/util/xml.rb b/lib/grape/util/xml.rb index dd5d296e6..d948f8012 100644 --- a/lib/grape/util/xml.rb +++ b/lib/grape/util/xml.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape if Object.const_defined? :MultiXml Xml = ::MultiXml diff --git a/lib/grape/validations.rb b/lib/grape/validations.rb index c5771b520..bd55c0611 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape # Registry to store and locate known Validators. module Validations diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index 5a9609b70..40dc91edf 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class AttributesIterator diff --git a/lib/grape/validations/multiple_attributes_iterator.rb b/lib/grape/validations/multiple_attributes_iterator.rb index 95c6b27c9..49c7c2bc6 100644 --- a/lib/grape/validations/multiple_attributes_iterator.rb +++ b/lib/grape/validations/multiple_attributes_iterator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class MultipleAttributesIterator < AttributesIterator diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index c1192e580..9c9fbbe74 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class ParamsScope diff --git a/lib/grape/validations/single_attribute_iterator.rb b/lib/grape/validations/single_attribute_iterator.rb index 1ad06fe71..a76cfa836 100644 --- a/lib/grape/validations/single_attribute_iterator.rb +++ b/lib/grape/validations/single_attribute_iterator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class SingleAttributeIterator < AttributesIterator diff --git a/lib/grape/validations/types.rb b/lib/grape/validations/types.rb index ee2ab22ed..ee60949d8 100644 --- a/lib/grape/validations/types.rb +++ b/lib/grape/validations/types.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'types/build_coercer' require_relative 'types/custom_type_coercer' require_relative 'types/custom_type_collection_coercer' diff --git a/lib/grape/validations/types/array_coercer.rb b/lib/grape/validations/types/array_coercer.rb index 57174fa68..4f456a741 100644 --- a/lib/grape/validations/types/array_coercer.rb +++ b/lib/grape/validations/types/array_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'dry_type_coercer' module Grape diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index 9ed1e5d6f..fc0762121 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'array_coercer' require_relative 'set_coercer' require_relative 'primitive_coercer' diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index 9a6b4da18..70ac7b20a 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations module Types diff --git a/lib/grape/validations/types/custom_type_collection_coercer.rb b/lib/grape/validations/types/custom_type_collection_coercer.rb index ebdae4d40..2a5a002f4 100644 --- a/lib/grape/validations/types/custom_type_collection_coercer.rb +++ b/lib/grape/validations/types/custom_type_collection_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations module Types diff --git a/lib/grape/validations/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb index a0a9841c1..a1c5fb36f 100644 --- a/lib/grape/validations/types/dry_type_coercer.rb +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'dry-types' module DryTypes diff --git a/lib/grape/validations/types/file.rb b/lib/grape/validations/types/file.rb index 0a47bc370..2802ace48 100644 --- a/lib/grape/validations/types/file.rb +++ b/lib/grape/validations/types/file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations module Types diff --git a/lib/grape/validations/types/json.rb b/lib/grape/validations/types/json.rb index c464477b1..d401cdc9e 100644 --- a/lib/grape/validations/types/json.rb +++ b/lib/grape/validations/types/json.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'json' module Grape diff --git a/lib/grape/validations/types/multiple_type_coercer.rb b/lib/grape/validations/types/multiple_type_coercer.rb index 215030022..304746ae2 100644 --- a/lib/grape/validations/types/multiple_type_coercer.rb +++ b/lib/grape/validations/types/multiple_type_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations module Types diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index d46247bda..0e4683396 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'dry_type_coercer' module Grape diff --git a/lib/grape/validations/types/set_coercer.rb b/lib/grape/validations/types/set_coercer.rb index 402dd60c1..1ddc887c2 100644 --- a/lib/grape/validations/types/set_coercer.rb +++ b/lib/grape/validations/types/set_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'set' require_relative 'dry_type_coercer' diff --git a/lib/grape/validations/types/variant_collection_coercer.rb b/lib/grape/validations/types/variant_collection_coercer.rb index d841b6e55..9495c891c 100644 --- a/lib/grape/validations/types/variant_collection_coercer.rb +++ b/lib/grape/validations/types/variant_collection_coercer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations module Types diff --git a/lib/grape/validations/validator_factory.rb b/lib/grape/validations/validator_factory.rb index fe849cc79..f23655f10 100644 --- a/lib/grape/validations/validator_factory.rb +++ b/lib/grape/validations/validator_factory.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class ValidatorFactory diff --git a/lib/grape/validations/validators/all_or_none.rb b/lib/grape/validations/validators/all_or_none.rb index 5062044c9..e807d018b 100644 --- a/lib/grape/validations/validators/all_or_none.rb +++ b/lib/grape/validations/validators/all_or_none.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/validations/validators/multiple_params_base' module Grape diff --git a/lib/grape/validations/validators/allow_blank.rb b/lib/grape/validations/validators/allow_blank.rb index d0c314e7a..8230214b8 100644 --- a/lib/grape/validations/validators/allow_blank.rb +++ b/lib/grape/validations/validators/allow_blank.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class AllowBlankValidator < Base diff --git a/lib/grape/validations/validators/as.rb b/lib/grape/validations/validators/as.rb index ffbbd6567..07ac8006d 100644 --- a/lib/grape/validations/validators/as.rb +++ b/lib/grape/validations/validators/as.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class AsValidator < Base diff --git a/lib/grape/validations/validators/at_least_one_of.rb b/lib/grape/validations/validators/at_least_one_of.rb index 5859d41d4..ba2f17928 100644 --- a/lib/grape/validations/validators/at_least_one_of.rb +++ b/lib/grape/validations/validators/at_least_one_of.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/validations/validators/multiple_params_base' module Grape diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 0594116eb..22997e8bc 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class Base diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index f21bbaadf..c104d69e4 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape class API class Boolean diff --git a/lib/grape/validations/validators/default.rb b/lib/grape/validations/validators/default.rb index 102458f14..c6c16529e 100644 --- a/lib/grape/validations/validators/default.rb +++ b/lib/grape/validations/validators/default.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class DefaultValidator < Base diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index 36ca3b10d..4a22970f1 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/validations/validators/multiple_params_base' module Grape diff --git a/lib/grape/validations/validators/except_values.rb b/lib/grape/validations/validators/except_values.rb index a98fc193d..9c5d6c040 100644 --- a/lib/grape/validations/validators/except_values.rb +++ b/lib/grape/validations/validators/except_values.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class ExceptValuesValidator < Base diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index 023e2f5c9..013386b59 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class MultipleParamsBase < Base diff --git a/lib/grape/validations/validators/mutual_exclusion.rb b/lib/grape/validations/validators/mutual_exclusion.rb index efe849f7c..a3b21488d 100644 --- a/lib/grape/validations/validators/mutual_exclusion.rb +++ b/lib/grape/validations/validators/mutual_exclusion.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'grape/validations/validators/multiple_params_base' module Grape diff --git a/lib/grape/validations/validators/presence.rb b/lib/grape/validations/validators/presence.rb index eddde01dc..3d32c7d7c 100644 --- a/lib/grape/validations/validators/presence.rb +++ b/lib/grape/validations/validators/presence.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class PresenceValidator < Base diff --git a/lib/grape/validations/validators/regexp.rb b/lib/grape/validations/validators/regexp.rb index 749a42ce2..a6bbd7494 100644 --- a/lib/grape/validations/validators/regexp.rb +++ b/lib/grape/validations/validators/regexp.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class RegexpValidator < Base diff --git a/lib/grape/validations/validators/same_as.rb b/lib/grape/validations/validators/same_as.rb index 126ba3f4d..b7f63b8c5 100644 --- a/lib/grape/validations/validators/same_as.rb +++ b/lib/grape/validations/validators/same_as.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class SameAsValidator < Base diff --git a/lib/grape/validations/validators/values.rb b/lib/grape/validations/validators/values.rb index 08f019b52..a7486acb1 100644 --- a/lib/grape/validations/validators/values.rb +++ b/lib/grape/validations/validators/values.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Grape module Validations class ValuesValidator < Base diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 1b220b89f..00faa5f52 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -1,4 +1,6 @@ +# frozen_string_literal: true + module Grape # The current version of Grape. - VERSION = '1.2.6'.freeze + VERSION = '1.2.6' end diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index 278beb7a9..bb12b877f 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations do diff --git a/spec/grape/api/deeply_included_options_spec.rb b/spec/grape/api/deeply_included_options_spec.rb index 23710e427..71cc1385b 100644 --- a/spec/grape/api/deeply_included_options_spec.rb +++ b/spec/grape/api/deeply_included_options_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module DeeplyIncludedOptionsSpec diff --git a/spec/grape/api/defines_boolean_in_params_spec.rb b/spec/grape/api/defines_boolean_in_params_spec.rb index d057bbbce..8a0302a23 100644 --- a/spec/grape/api/defines_boolean_in_params_spec.rb +++ b/spec/grape/api/defines_boolean_in_params_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API::Instance do diff --git a/spec/grape/api/inherited_helpers_spec.rb b/spec/grape/api/inherited_helpers_spec.rb index a6c0695d8..be2cb9179 100644 --- a/spec/grape/api/inherited_helpers_spec.rb +++ b/spec/grape/api/inherited_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API::Helpers do diff --git a/spec/grape/api/invalid_format_spec.rb b/spec/grape/api/invalid_format_spec.rb index 574769448..e3e78f7be 100644 --- a/spec/grape/api/invalid_format_spec.rb +++ b/spec/grape/api/invalid_format_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/api/namespace_parameters_in_route_spec.rb b/spec/grape/api/namespace_parameters_in_route_spec.rb index 0baa341ba..e8496a4a5 100644 --- a/spec/grape/api/namespace_parameters_in_route_spec.rb +++ b/spec/grape/api/namespace_parameters_in_route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/api/nested_helpers_spec.rb b/spec/grape/api/nested_helpers_spec.rb index fb3f7d3f0..2f9c61aba 100644 --- a/spec/grape/api/nested_helpers_spec.rb +++ b/spec/grape/api/nested_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API::Helpers do diff --git a/spec/grape/api/optional_parameters_in_route_spec.rb b/spec/grape/api/optional_parameters_in_route_spec.rb index 18aacbd5d..6bc6bc47d 100644 --- a/spec/grape/api/optional_parameters_in_route_spec.rb +++ b/spec/grape/api/optional_parameters_in_route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/api/parameters_modification_spec.rb b/spec/grape/api/parameters_modification_spec.rb index 7bff02d5b..2d1ea1e20 100644 --- a/spec/grape/api/parameters_modification_spec.rb +++ b/spec/grape/api/parameters_modification_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do @@ -10,7 +12,7 @@ def app before do subject.namespace :test do params do - optional :foo, default: '-abcdef' + optional :foo, default: +'-abcdef' end get do params[:foo].slice!(0) diff --git a/spec/grape/api/patch_method_helpers_spec.rb b/spec/grape/api/patch_method_helpers_spec.rb index 71d27ec7f..a369a284a 100644 --- a/spec/grape/api/patch_method_helpers_spec.rb +++ b/spec/grape/api/patch_method_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API::Helpers do diff --git a/spec/grape/api/recognize_path_spec.rb b/spec/grape/api/recognize_path_spec.rb index 5b86a0789..0d821f57b 100644 --- a/spec/grape/api/recognize_path_spec.rb +++ b/spec/grape/api/recognize_path_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API do diff --git a/spec/grape/api/required_parameters_in_route_spec.rb b/spec/grape/api/required_parameters_in_route_spec.rb index bc0f05e97..63544892a 100644 --- a/spec/grape/api/required_parameters_in_route_spec.rb +++ b/spec/grape/api/required_parameters_in_route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/api/required_parameters_with_invalid_method_spec.rb b/spec/grape/api/required_parameters_with_invalid_method_spec.rb index 3a8b0856f..0829492cf 100644 --- a/spec/grape/api/required_parameters_with_invalid_method_spec.rb +++ b/spec/grape/api/required_parameters_with_invalid_method_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/api/routes_with_requirements_spec.rb b/spec/grape/api/routes_with_requirements_spec.rb index c6ce2ccee..83c5b85ec 100644 --- a/spec/grape/api/routes_with_requirements_spec.rb +++ b/spec/grape/api/routes_with_requirements_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/api/shared_helpers_exactly_one_of_spec.rb b/spec/grape/api/shared_helpers_exactly_one_of_spec.rb index a6d0cb22f..049fd8706 100644 --- a/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +++ b/spec/grape/api/shared_helpers_exactly_one_of_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API::Helpers do diff --git a/spec/grape/api/shared_helpers_spec.rb b/spec/grape/api/shared_helpers_spec.rb index 3b0a85a9e..68626944d 100644 --- a/spec/grape/api/shared_helpers_spec.rb +++ b/spec/grape/api/shared_helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API::Helpers do diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index f0f60a2a5..2f7501765 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'shared/versioning_examples' diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 8db185c75..68888f6ee 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'shared/versioning_examples' diff --git a/spec/grape/config_spec.rb b/spec/grape/config_spec.rb index f9290e3c7..07bed04a3 100644 --- a/spec/grape/config_spec.rb +++ b/spec/grape/config_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe '.configure' do diff --git a/spec/grape/dsl/callbacks_spec.rb b/spec/grape/dsl/callbacks_spec.rb index db3fb7952..73dbc259e 100644 --- a/spec/grape/dsl/callbacks_spec.rb +++ b/spec/grape/dsl/callbacks_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/configuration_spec.rb b/spec/grape/dsl/configuration_spec.rb index 6216b007c..32b015f75 100644 --- a/spec/grape/dsl/configuration_spec.rb +++ b/spec/grape/dsl/configuration_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/desc_spec.rb b/spec/grape/dsl/desc_spec.rb index 52ba42660..9822620c0 100644 --- a/spec/grape/dsl/desc_spec.rb +++ b/spec/grape/dsl/desc_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/headers_spec.rb b/spec/grape/dsl/headers_spec.rb index de5763f10..d23652d07 100644 --- a/spec/grape/dsl/headers_spec.rb +++ b/spec/grape/dsl/headers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/helpers_spec.rb b/spec/grape/dsl/helpers_spec.rb index c1ce8de70..2f7bbf5a1 100644 --- a/spec/grape/dsl/helpers_spec.rb +++ b/spec/grape/dsl/helpers_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/inside_route_spec.rb b/spec/grape/dsl/inside_route_spec.rb index 928510f75..51da952fc 100644 --- a/spec/grape/dsl/inside_route_spec.rb +++ b/spec/grape/dsl/inside_route_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/logger_spec.rb b/spec/grape/dsl/logger_spec.rb index 4a4ce831c..1992e3277 100644 --- a/spec/grape/dsl/logger_spec.rb +++ b/spec/grape/dsl/logger_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/middleware_spec.rb b/spec/grape/dsl/middleware_spec.rb index bacb18b00..b116fb735 100644 --- a/spec/grape/dsl/middleware_spec.rb +++ b/spec/grape/dsl/middleware_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/parameters_spec.rb b/spec/grape/dsl/parameters_spec.rb index 27af242a2..bd60195e1 100644 --- a/spec/grape/dsl/parameters_spec.rb +++ b/spec/grape/dsl/parameters_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/request_response_spec.rb b/spec/grape/dsl/request_response_spec.rb index 7974537fb..00d9e3a6a 100644 --- a/spec/grape/dsl/request_response_spec.rb +++ b/spec/grape/dsl/request_response_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/routing_spec.rb b/spec/grape/dsl/routing_spec.rb index 826b6eaa5..e26d039d3 100644 --- a/spec/grape/dsl/routing_spec.rb +++ b/spec/grape/dsl/routing_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/settings_spec.rb b/spec/grape/dsl/settings_spec.rb index 1620c4f7d..b2520d36f 100644 --- a/spec/grape/dsl/settings_spec.rb +++ b/spec/grape/dsl/settings_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/dsl/validations_spec.rb b/spec/grape/dsl/validations_spec.rb index a97bac5ed..e39069266 100644 --- a/spec/grape/dsl/validations_spec.rb +++ b/spec/grape/dsl/validations_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 91a900a64..13bc92a89 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Endpoint do diff --git a/spec/grape/entity_spec.rb b/spec/grape/entity_spec.rb index 35299dfb9..08c378ef2 100644 --- a/spec/grape/entity_spec.rb +++ b/spec/grape/entity_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'grape_entity' diff --git a/spec/grape/exceptions/base_spec.rb b/spec/grape/exceptions/base_spec.rb index f95531f7a..aaf898f55 100644 --- a/spec/grape/exceptions/base_spec.rb +++ b/spec/grape/exceptions/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::Base do diff --git a/spec/grape/exceptions/body_parse_errors_spec.rb b/spec/grape/exceptions/body_parse_errors_spec.rb index 422e6719e..990758a5f 100644 --- a/spec/grape/exceptions/body_parse_errors_spec.rb +++ b/spec/grape/exceptions/body_parse_errors_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::ValidationErrors do diff --git a/spec/grape/exceptions/invalid_accept_header_spec.rb b/spec/grape/exceptions/invalid_accept_header_spec.rb index d308620cc..3a93ce3e1 100644 --- a/spec/grape/exceptions/invalid_accept_header_spec.rb +++ b/spec/grape/exceptions/invalid_accept_header_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::InvalidAcceptHeader do diff --git a/spec/grape/exceptions/invalid_formatter_spec.rb b/spec/grape/exceptions/invalid_formatter_spec.rb index b63280d67..3c60e3d13 100644 --- a/spec/grape/exceptions/invalid_formatter_spec.rb +++ b/spec/grape/exceptions/invalid_formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::InvalidFormatter do diff --git a/spec/grape/exceptions/invalid_response_spec.rb b/spec/grape/exceptions/invalid_response_spec.rb index 4603ca13b..8a7c6879b 100644 --- a/spec/grape/exceptions/invalid_response_spec.rb +++ b/spec/grape/exceptions/invalid_response_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::InvalidResponse do diff --git a/spec/grape/exceptions/invalid_versioner_option_spec.rb b/spec/grape/exceptions/invalid_versioner_option_spec.rb index 5ef5124f6..a17079349 100644 --- a/spec/grape/exceptions/invalid_versioner_option_spec.rb +++ b/spec/grape/exceptions/invalid_versioner_option_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::InvalidVersionerOption do diff --git a/spec/grape/exceptions/missing_mime_type_spec.rb b/spec/grape/exceptions/missing_mime_type_spec.rb index 5ae6970a8..b5545cbcc 100644 --- a/spec/grape/exceptions/missing_mime_type_spec.rb +++ b/spec/grape/exceptions/missing_mime_type_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::MissingMimeType do diff --git a/spec/grape/exceptions/missing_option_spec.rb b/spec/grape/exceptions/missing_option_spec.rb index c8f638a78..1ae125134 100644 --- a/spec/grape/exceptions/missing_option_spec.rb +++ b/spec/grape/exceptions/missing_option_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::MissingOption do diff --git a/spec/grape/exceptions/unknown_options_spec.rb b/spec/grape/exceptions/unknown_options_spec.rb index 4b535cef2..743553719 100644 --- a/spec/grape/exceptions/unknown_options_spec.rb +++ b/spec/grape/exceptions/unknown_options_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::UnknownOptions do diff --git a/spec/grape/exceptions/unknown_validator_spec.rb b/spec/grape/exceptions/unknown_validator_spec.rb index 810f0d84f..2ef256f37 100644 --- a/spec/grape/exceptions/unknown_validator_spec.rb +++ b/spec/grape/exceptions/unknown_validator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::UnknownValidator do diff --git a/spec/grape/exceptions/validation_errors_spec.rb b/spec/grape/exceptions/validation_errors_spec.rb index 174350e6e..eb75608b5 100644 --- a/spec/grape/exceptions/validation_errors_spec.rb +++ b/spec/grape/exceptions/validation_errors_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'ostruct' diff --git a/spec/grape/exceptions/validation_spec.rb b/spec/grape/exceptions/validation_spec.rb index c1b5fba25..2eb4d3433 100644 --- a/spec/grape/exceptions/validation_spec.rb +++ b/spec/grape/exceptions/validation_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Exceptions::Validation do diff --git a/spec/grape/extensions/param_builders/hash_spec.rb b/spec/grape/extensions/param_builders/hash_spec.rb index 27371fd98..2b68397d0 100644 --- a/spec/grape/extensions/param_builders/hash_spec.rb +++ b/spec/grape/extensions/param_builders/hash_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Extensions::Hash::ParamBuilder do diff --git a/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb b/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb index 1c2ea2c4f..4e5b8e6b1 100644 --- a/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +++ b/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder do diff --git a/spec/grape/extensions/param_builders/hashie/mash_spec.rb b/spec/grape/extensions/param_builders/hashie/mash_spec.rb index 0a5975f2d..b533f5657 100644 --- a/spec/grape/extensions/param_builders/hashie/mash_spec.rb +++ b/spec/grape/extensions/param_builders/hashie/mash_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Extensions::Hashie::Mash::ParamBuilder do diff --git a/spec/grape/integration/global_namespace_function_spec.rb b/spec/grape/integration/global_namespace_function_spec.rb index 014280cd6..32a55f7de 100644 --- a/spec/grape/integration/global_namespace_function_spec.rb +++ b/spec/grape/integration/global_namespace_function_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # see https://github.com/ruby-grape/grape/issues/1348 require 'spec_helper' diff --git a/spec/grape/integration/rack_sendfile_spec.rb b/spec/grape/integration/rack_sendfile_spec.rb index 3136f7f21..8b4f34feb 100644 --- a/spec/grape/integration/rack_sendfile_spec.rb +++ b/spec/grape/integration/rack_sendfile_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Rack::Sendfile do diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index 872f07524..cc339ddec 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Rack do diff --git a/spec/grape/loading_spec.rb b/spec/grape/loading_spec.rb index a3ed88759..e28891cce 100644 --- a/spec/grape/loading_spec.rb +++ b/spec/grape/loading_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::API do diff --git a/spec/grape/middleware/auth/base_spec.rb b/spec/grape/middleware/auth/base_spec.rb index 2a2a6ab91..d18433698 100644 --- a/spec/grape/middleware/auth/base_spec.rb +++ b/spec/grape/middleware/auth/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'base64' diff --git a/spec/grape/middleware/auth/dsl_spec.rb b/spec/grape/middleware/auth/dsl_spec.rb index f39825a60..338d17c7c 100644 --- a/spec/grape/middleware/auth/dsl_spec.rb +++ b/spec/grape/middleware/auth/dsl_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Auth::DSL do diff --git a/spec/grape/middleware/auth/strategies_spec.rb b/spec/grape/middleware/auth/strategies_spec.rb index 9a43e7bcd..3917bc76e 100644 --- a/spec/grape/middleware/auth/strategies_spec.rb +++ b/spec/grape/middleware/auth/strategies_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'base64' diff --git a/spec/grape/middleware/base_spec.rb b/spec/grape/middleware/base_spec.rb index 0e4a8de70..02a745e11 100644 --- a/spec/grape/middleware/base_spec.rb +++ b/spec/grape/middleware/base_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Base do diff --git a/spec/grape/middleware/error_spec.rb b/spec/grape/middleware/error_spec.rb index b71fe0f22..66901292b 100644 --- a/spec/grape/middleware/error_spec.rb +++ b/spec/grape/middleware/error_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'grape-entity' diff --git a/spec/grape/middleware/exception_spec.rb b/spec/grape/middleware/exception_spec.rb index 608e1f5da..708c1885d 100644 --- a/spec/grape/middleware/exception_spec.rb +++ b/spec/grape/middleware/exception_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Error do diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index f3bc98b91..e26ba43f5 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Formatter do @@ -41,7 +43,7 @@ def to_json end context 'xml' do - let(:body) { 'string' } + let(:body) { +'string' } it 'calls #to_xml if the content type is xml' do body.instance_eval do def to_xml diff --git a/spec/grape/middleware/globals_spec.rb b/spec/grape/middleware/globals_spec.rb index d63c289e3..e50eff171 100644 --- a/spec/grape/middleware/globals_spec.rb +++ b/spec/grape/middleware/globals_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Globals do diff --git a/spec/grape/middleware/stack_spec.rb b/spec/grape/middleware/stack_spec.rb index ff0062fba..7579dc1d0 100644 --- a/spec/grape/middleware/stack_spec.rb +++ b/spec/grape/middleware/stack_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Stack do diff --git a/spec/grape/middleware/versioner/accept_version_header_spec.rb b/spec/grape/middleware/versioner/accept_version_header_spec.rb index efd95f344..03a204b56 100644 --- a/spec/grape/middleware/versioner/accept_version_header_spec.rb +++ b/spec/grape/middleware/versioner/accept_version_header_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Versioner::AcceptVersionHeader do diff --git a/spec/grape/middleware/versioner/header_spec.rb b/spec/grape/middleware/versioner/header_spec.rb index aaedc757d..fd85061ac 100644 --- a/spec/grape/middleware/versioner/header_spec.rb +++ b/spec/grape/middleware/versioner/header_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Versioner::Header do diff --git a/spec/grape/middleware/versioner/param_spec.rb b/spec/grape/middleware/versioner/param_spec.rb index cdf3d23a8..714b07a98 100644 --- a/spec/grape/middleware/versioner/param_spec.rb +++ b/spec/grape/middleware/versioner/param_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Versioner::Param do diff --git a/spec/grape/middleware/versioner/path_spec.rb b/spec/grape/middleware/versioner/path_spec.rb index c5e9995d8..33ec99aa1 100644 --- a/spec/grape/middleware/versioner/path_spec.rb +++ b/spec/grape/middleware/versioner/path_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Versioner::Path do diff --git a/spec/grape/middleware/versioner_spec.rb b/spec/grape/middleware/versioner_spec.rb index d3f84cd35..198f15a3a 100644 --- a/spec/grape/middleware/versioner_spec.rb +++ b/spec/grape/middleware/versioner_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Middleware::Versioner do diff --git a/spec/grape/named_api_spec.rb b/spec/grape/named_api_spec.rb index c8b9ee501..145066612 100644 --- a/spec/grape/named_api_spec.rb +++ b/spec/grape/named_api_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'A named API' do diff --git a/spec/grape/parser_spec.rb b/spec/grape/parser_spec.rb index f9b6c1d52..38ffbc03d 100644 --- a/spec/grape/parser_spec.rb +++ b/spec/grape/parser_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Parser do diff --git a/spec/grape/path_spec.rb b/spec/grape/path_spec.rb index 721b69f24..e5a2f1c7e 100644 --- a/spec/grape/path_spec.rb +++ b/spec/grape/path_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/presenters/presenter_spec.rb b/spec/grape/presenters/presenter_spec.rb index 71bb6533c..4e73e8e5b 100644 --- a/spec/grape/presenters/presenter_spec.rb +++ b/spec/grape/presenters/presenter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/request_spec.rb b/spec/grape/request_spec.rb index 2c352d175..ee5a43643 100644 --- a/spec/grape/request_spec.rb +++ b/spec/grape/request_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape diff --git a/spec/grape/util/inheritable_setting_spec.rb b/spec/grape/util/inheritable_setting_spec.rb index 866a7bbd3..0e0b672e7 100644 --- a/spec/grape/util/inheritable_setting_spec.rb +++ b/spec/grape/util/inheritable_setting_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape module Util diff --git a/spec/grape/util/inheritable_values_spec.rb b/spec/grape/util/inheritable_values_spec.rb index af2eb9d5a..a5003d875 100644 --- a/spec/grape/util/inheritable_values_spec.rb +++ b/spec/grape/util/inheritable_values_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape module Util diff --git a/spec/grape/util/reverse_stackable_values_spec.rb b/spec/grape/util/reverse_stackable_values_spec.rb index 6c2d6b543..c5ffbd293 100644 --- a/spec/grape/util/reverse_stackable_values_spec.rb +++ b/spec/grape/util/reverse_stackable_values_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape module Util diff --git a/spec/grape/util/stackable_values_spec.rb b/spec/grape/util/stackable_values_spec.rb index 82a327a56..07e6e4a98 100644 --- a/spec/grape/util/stackable_values_spec.rb +++ b/spec/grape/util/stackable_values_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape module Util diff --git a/spec/grape/util/strict_hash_configuration_spec.rb b/spec/grape/util/strict_hash_configuration_spec.rb index 8798d095f..c55fd616b 100644 --- a/spec/grape/util/strict_hash_configuration_spec.rb +++ b/spec/grape/util/strict_hash_configuration_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' module Grape module Util diff --git a/spec/grape/validations/attributes_iterator_spec.rb b/spec/grape/validations/attributes_iterator_spec.rb index 7a8e4ec31..594c8ca19 100644 --- a/spec/grape/validations/attributes_iterator_spec.rb +++ b/spec/grape/validations/attributes_iterator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::AttributesIterator do diff --git a/spec/grape/validations/instance_behaivour_spec.rb b/spec/grape/validations/instance_behaivour_spec.rb index 1c823b5ac..fb10912ee 100644 --- a/spec/grape/validations/instance_behaivour_spec.rb +++ b/spec/grape/validations/instance_behaivour_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'Validator with instance variables' do diff --git a/spec/grape/validations/multiple_attributes_iterator_spec.rb b/spec/grape/validations/multiple_attributes_iterator_spec.rb index ef7ead6f6..85f848207 100644 --- a/spec/grape/validations/multiple_attributes_iterator_spec.rb +++ b/spec/grape/validations/multiple_attributes_iterator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::MultipleAttributesIterator do diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 6faf5680b..984524c8f 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::ParamsScope do diff --git a/spec/grape/validations/single_attribute_iterator_spec.rb b/spec/grape/validations/single_attribute_iterator_spec.rb index ab884f702..abce54d86 100644 --- a/spec/grape/validations/single_attribute_iterator_spec.rb +++ b/spec/grape/validations/single_attribute_iterator_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::SingleAttributeIterator do diff --git a/spec/grape/validations/types_spec.rb b/spec/grape/validations/types_spec.rb index 1380b1016..133cba130 100644 --- a/spec/grape/validations/types_spec.rb +++ b/spec/grape/validations/types_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::Types do diff --git a/spec/grape/validations/validators/all_or_none_spec.rb b/spec/grape/validations/validators/all_or_none_spec.rb index 31abc57cf..ce4124946 100644 --- a/spec/grape/validations/validators/all_or_none_spec.rb +++ b/spec/grape/validations/validators/all_or_none_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::AllOrNoneOfValidator do diff --git a/spec/grape/validations/validators/allow_blank_spec.rb b/spec/grape/validations/validators/allow_blank_spec.rb index 643ba04be..cb74e332c 100644 --- a/spec/grape/validations/validators/allow_blank_spec.rb +++ b/spec/grape/validations/validators/allow_blank_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::AllowBlankValidator do diff --git a/spec/grape/validations/validators/at_least_one_of_spec.rb b/spec/grape/validations/validators/at_least_one_of_spec.rb index 35044c899..b6468e3e5 100644 --- a/spec/grape/validations/validators/at_least_one_of_spec.rb +++ b/spec/grape/validations/validators/at_least_one_of_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::AtLeastOneOfValidator do diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 1c956f6fd..69655850f 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::CoerceValidator do diff --git a/spec/grape/validations/validators/default_spec.rb b/spec/grape/validations/validators/default_spec.rb index 3b6dea50e..f110fe0e8 100644 --- a/spec/grape/validations/validators/default_spec.rb +++ b/spec/grape/validations/validators/default_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::DefaultValidator do diff --git a/spec/grape/validations/validators/exactly_one_of_spec.rb b/spec/grape/validations/validators/exactly_one_of_spec.rb index de06414dc..143921c11 100644 --- a/spec/grape/validations/validators/exactly_one_of_spec.rb +++ b/spec/grape/validations/validators/exactly_one_of_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::ExactlyOneOfValidator do diff --git a/spec/grape/validations/validators/except_values_spec.rb b/spec/grape/validations/validators/except_values_spec.rb index 9e0787697..1bdbfc805 100644 --- a/spec/grape/validations/validators/except_values_spec.rb +++ b/spec/grape/validations/validators/except_values_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::ExceptValuesValidator do diff --git a/spec/grape/validations/validators/mutual_exclusion_spec.rb b/spec/grape/validations/validators/mutual_exclusion_spec.rb index 0e8fbb06c..ac1b46989 100644 --- a/spec/grape/validations/validators/mutual_exclusion_spec.rb +++ b/spec/grape/validations/validators/mutual_exclusion_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::MutualExclusionValidator do diff --git a/spec/grape/validations/validators/presence_spec.rb b/spec/grape/validations/validators/presence_spec.rb index 7de97190a..5eaabe72a 100644 --- a/spec/grape/validations/validators/presence_spec.rb +++ b/spec/grape/validations/validators/presence_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::PresenceValidator do diff --git a/spec/grape/validations/validators/regexp_spec.rb b/spec/grape/validations/validators/regexp_spec.rb index cc2970a88..b4ffc99df 100644 --- a/spec/grape/validations/validators/regexp_spec.rb +++ b/spec/grape/validations/validators/regexp_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::RegexpValidator do diff --git a/spec/grape/validations/validators/same_as_spec.rb b/spec/grape/validations/validators/same_as_spec.rb index 37f10fc45..da4c945c0 100644 --- a/spec/grape/validations/validators/same_as_spec.rb +++ b/spec/grape/validations/validators/same_as_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::SameAsValidator do diff --git a/spec/grape/validations/validators/values_spec.rb b/spec/grape/validations/validators/values_spec.rb index 2317cf555..ee6ec04e8 100644 --- a/spec/grape/validations/validators/values_spec.rb +++ b/spec/grape/validations/validators/values_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations::ValuesValidator do diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 7650828fe..5ee7d4028 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Validations do diff --git a/spec/integration/multi_json/json_spec.rb b/spec/integration/multi_json/json_spec.rb index 11ae85132..fc06602fd 100644 --- a/spec/integration/multi_json/json_spec.rb +++ b/spec/integration/multi_json/json_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Json do diff --git a/spec/integration/multi_xml/xml_spec.rb b/spec/integration/multi_xml/xml_spec.rb index fce3c51ed..dde1fec5c 100644 --- a/spec/integration/multi_xml/xml_spec.rb +++ b/spec/integration/multi_xml/xml_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe Grape::Xml do diff --git a/spec/shared/versioning_examples.rb b/spec/shared/versioning_examples.rb index 47113c5ec..8e8552070 100644 --- a/spec/shared/versioning_examples.rb +++ b/spec/shared/versioning_examples.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + shared_examples_for 'versioning' do it 'sets the API version' do subject.format :txt diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1653756ad..269958695 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.dirname(__FILE__)) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support')) diff --git a/spec/support/basic_auth_encode_helpers.rb b/spec/support/basic_auth_encode_helpers.rb index 0a841aa4a..c58acada6 100644 --- a/spec/support/basic_auth_encode_helpers.rb +++ b/spec/support/basic_auth_encode_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spec module Support module Helpers diff --git a/spec/support/content_type_helpers.rb b/spec/support/content_type_helpers.rb index 5a23f0260..9721edacd 100644 --- a/spec/support/content_type_helpers.rb +++ b/spec/support/content_type_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spec module Support module Helpers diff --git a/spec/support/endpoint_faker.rb b/spec/support/endpoint_faker.rb index 2d9c82f8a..1cf02ef1d 100644 --- a/spec/support/endpoint_faker.rb +++ b/spec/support/endpoint_faker.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spec module Support class EndpointFaker diff --git a/spec/support/file_streamer.rb b/spec/support/file_streamer.rb index 8a9f24d09..b640fba27 100644 --- a/spec/support/file_streamer.rb +++ b/spec/support/file_streamer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class FileStreamer def initialize(file_path) @file_path = file_path diff --git a/spec/support/integer_helpers.rb b/spec/support/integer_helpers.rb index 88fba900f..670cb52a6 100644 --- a/spec/support/integer_helpers.rb +++ b/spec/support/integer_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Spec module Support module Helpers diff --git a/spec/support/versioned_helpers.rb b/spec/support/versioned_helpers.rb index 7423bdf92..acd48ccf4 100644 --- a/spec/support/versioned_helpers.rb +++ b/spec/support/versioned_helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Versioning module Spec module Support From eceea5243c9c936f0fcd552a7030c67438d77020 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 9 Dec 2019 23:49:55 +0100 Subject: [PATCH 146/290] Retained Grape::Http::Headers in memory instead of new ones created. Changelog Keep the original route.request_method since its already sanitized Moved || outside of the function. Renamed local variables --- CHANGELOG.md | 1 + lib/grape/http/headers.rb | 4 ++++ lib/grape/router.rb | 2 +- lib/grape/router/route.rb | 7 ++++--- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5afb0324..c9eb3604f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimized retained memory methods - [@ericproulx](https://github.com/ericproulx). * [#1941](https://github.com/ruby-grape/grape/pull/1941): Frozen string literal - [@ericproulx](https://github.com/ericproulx). * [#1940](https://github.com/ruby-grape/grape/pull/1940): Get rid of a needless step in HashWithIndifferentAccess - [@dnesteryuk](https://github.com/dnesteryuk). * [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien). diff --git a/lib/grape/http/headers.rb b/lib/grape/http/headers.rb index ddecc2b8b..20419e131 100644 --- a/lib/grape/http/headers.rb +++ b/lib/grape/http/headers.rb @@ -26,6 +26,10 @@ module Headers HTTP_ACCEPT = 'HTTP_ACCEPT' FORMAT = 'format' + + def self.find_supported_method(route_method) + Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? } + end end end end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 249602839..970699004 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -46,7 +46,7 @@ def compile! end def append(route) - map[route.request_method.to_s.upcase] << route + map[route.request_method] << route end def associate_routes(pattern, **options) diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 18caf9488..d0b3e4290 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -64,12 +64,13 @@ def route_path end def initialize(method, pattern, **options) - upcased_method = method.to_s.upcase + method_s = method.to_s + method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase @suffix = options[:suffix] - @options = options.merge(method: upcased_method) + @options = options.merge(method: method_upcase) @pattern = Pattern.new(pattern, **options) - @translator = AttributeTranslator.new(**options, request_method: upcased_method) + @translator = AttributeTranslator.new(**options, request_method: method_upcase) end def exec(env) From afcca35789401373f48907bc2df64104c82c57f6 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 15 Dec 2019 20:32:29 +0100 Subject: [PATCH 147/290] AttributeTranslator optimization (#1944) Added request_method and requirements has methods instead using method_missing since it's defined by defaults. Added regexp and index in Any class instead of falling back on method_missing --- CHANGELOG.md | 1 + lib/grape/router.rb | 7 +++++-- lib/grape/router/attribute_translator.rb | 24 ++++++++++++++++-------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9eb3604f..7721c0577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduced attribute_translator string allocations - [@ericproulx](https://github.com/ericproulx). * [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimized retained memory methods - [@ericproulx](https://github.com/ericproulx). * [#1941](https://github.com/ruby-grape/grape/pull/1941): Frozen string literal - [@ericproulx](https://github.com/ericproulx). * [#1940](https://github.com/ruby-grape/grape/pull/1940): Get rid of a needless step in HashWithIndifferentAccess - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 970699004..509c1a273 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -7,8 +7,11 @@ class Router attr_reader :map, :compiled class Any < AttributeTranslator - def initialize(pattern, **attributes) + attr_reader :pattern, :regexp, :index + def initialize(pattern, regexp, index, **attributes) @pattern = pattern + @regexp = regexp + @index = index super(attributes) end end @@ -51,7 +54,7 @@ def append(route) def associate_routes(pattern, **options) regexp = /(?<_#{@neutral_map.length}>)#{pattern.to_regexp}/ - @neutral_map << Any.new(pattern, regexp: regexp, index: @neutral_map.length, **options) + @neutral_map << Any.new(pattern, regexp, @neutral_map.length, **options) end def call(env) diff --git a/lib/grape/router/attribute_translator.rb b/lib/grape/router/attribute_translator.rb index aa66662ff..ad2d03855 100644 --- a/lib/grape/router/attribute_translator.rb +++ b/lib/grape/router/attribute_translator.rb @@ -4,31 +4,39 @@ module Grape class Router # this could be an OpenStruct, but doesn't work in Ruby 2.3.0, see https://bugs.ruby-lang.org/issues/12251 class AttributeTranslator + attr_reader :attributes, :request_method, :requirements + def initialize(attributes = {}) @attributes = attributes + @request_method = attributes[:request_method] + @requirements = attributes[:requirements] end def to_h - @attributes + attributes end - def method_missing(m, *args) - if m[-1] == '=' - @attributes[m[0..-1]] = *args - elsif m[-1] != '=' - @attributes[m] + def method_missing(method_name, *args) # rubocop:disable Style/MethodMissing + if setter?(method_name[-1]) + attributes[method_name[0..-1]] = *args else - super + attributes[method_name] end end def respond_to_missing?(method_name, _include_private = false) - if method_name[-1] == '=' + if setter?(method_name[-1]) true else @attributes.key?(method_name) end end + + private + + def setter?(method_name) + method_name[-1] == '=' + end end end end From d30b780900d9fc3706614d1c160a8cff9c38819c Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 16 Dec 2019 14:28:41 +0100 Subject: [PATCH 148/290] Regex string allocation (#1943) Use Regexp instead of // for regex in router.rb for a more efficient string allocation. --- CHANGELOG.md | 1 + lib/grape/router.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7721c0577..d9f353a23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Your contribution here. * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduced attribute_translator string allocations - [@ericproulx](https://github.com/ericproulx). +* [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduce number of regex string allocations - [@ericproulx](https://github.com/ericproulx). * [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimized retained memory methods - [@ericproulx](https://github.com/ericproulx). * [#1941](https://github.com/ruby-grape/grape/pull/1941): Frozen string literal - [@ericproulx](https://github.com/ericproulx). * [#1940](https://github.com/ruby-grape/grape/pull/1940): Get rid of a needless step in HashWithIndifferentAccess - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 509c1a273..b30908842 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -41,7 +41,7 @@ def compile! routes = map[method] @optimized_map[method] = routes.map.with_index do |route, index| route.index = index - route.regexp = /(?<_#{index}>#{route.pattern.to_regexp})/ + route.regexp = Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})") end @optimized_map[method] = Regexp.union(@optimized_map[method]) end @@ -53,7 +53,7 @@ def append(route) end def associate_routes(pattern, **options) - regexp = /(?<_#{@neutral_map.length}>)#{pattern.to_regexp}/ + regexp = Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}") @neutral_map << Any.new(pattern, regexp, @neutral_map.length, **options) end From c7f0ac21aa1db3460de05246747869f37c1a04b3 Mon Sep 17 00:00:00 2001 From: nicolashopin <57815089+nicolashopin@users.noreply.github.com> Date: Tue, 17 Dec 2019 19:50:44 +0000 Subject: [PATCH 149/290] Fixes issue by checking if there's a base is there at all (#1946) --- CHANGELOG.md | 2 +- lib/grape/api/instance.rb | 2 +- spec/grape/api/instance_spec.rb | 48 +++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 spec/grape/api/instance_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f353a23..95b77a5dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ #### Fixes -* Your contribution here. +* [#1931](https://github.com/ruby-grape/grape/pull/1946): Fixes issue when using namespaces in `Grape::API::Instance` mounted directly - [@myxoh](https://github.com/myxoh). ### 1.2.5 (2019/12/01) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 8efa5568a..03358fd79 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -121,7 +121,7 @@ def evaluate_as_instance_with_configuration(block, lazy: false) self.configuration = value_for_configuration response end - if base_instance? && lazy + if base && base_instance? && lazy lazy_block else lazy_block.evaluate_from(configuration) diff --git a/spec/grape/api/instance_spec.rb b/spec/grape/api/instance_spec.rb new file mode 100644 index 000000000..6de77c51e --- /dev/null +++ b/spec/grape/api/instance_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared/versioning_examples' + +describe Grape::API::Instance do + subject(:an_instance) do + Class.new(Grape::API::Instance) do + namespace :some_namespace do + get 'some_endpoint' do + 'success' + end + end + end + end + + let(:root_api) do + to_mount = an_instance + Class.new(Grape::API) do + mount to_mount + end + end + + def app + root_api + end + + context 'when an instance is mounted on the root' do + it 'can call the instance endpoint' do + get '/some_namespace/some_endpoint' + expect(last_response.body).to eq 'success' + end + end + + context 'when an instance is the root' do + let(:root_api) do + to_mount = an_instance + Class.new(Grape::API::Instance) do + mount to_mount + end + end + + it 'can call the instance endpoint' do + get '/some_namespace/some_endpoint' + expect(last_response.body).to eq 'success' + end + end +end From 28d34ee19bdf70cb8471535b4c76312d440a63a6 Mon Sep 17 00:00:00 2001 From: Emily Stolfo Date: Wed, 13 Nov 2019 14:38:08 +0100 Subject: [PATCH 150/290] Move block call to separate method so it can be spied on --- .rubocop_todo.yml | 2 +- CHANGELOG.md | 15 ++++++++------- lib/grape/endpoint.rb | 6 +++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 627a6dca3..f63d8ee2a 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -44,7 +44,7 @@ Metrics/BlockLength: # Offense count: 10 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 302 + Max: 305 # Offense count: 30 Metrics/CyclomaticComplexity: diff --git a/CHANGELOG.md b/CHANGELOG.md index 95b77a5dd..dc21ebf1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,14 @@ #### Features * Your contribution here. -* [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduced attribute_translator string allocations - [@ericproulx](https://github.com/ericproulx). -* [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduce number of regex string allocations - [@ericproulx](https://github.com/ericproulx). -* [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimized retained memory methods - [@ericproulx](https://github.com/ericproulx). -* [#1941](https://github.com/ruby-grape/grape/pull/1941): Frozen string literal - [@ericproulx](https://github.com/ericproulx). -* [#1940](https://github.com/ruby-grape/grape/pull/1940): Get rid of a needless step in HashWithIndifferentAccess - [@dnesteryuk](https://github.com/dnesteryuk). -* [#1938](https://github.com/ruby-grape/grape/pull/1938): Add project metadata to the gemspec - [@orien](https://github.com/orien). -* [#1920](https://github.com/ruby-grape/grape/pull/1920): Replace Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk). +* [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). +* [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduces number of regex string allocations - [@ericproulx](https://github.com/ericproulx). +* [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimizes retained memory methods - [@ericproulx](https://github.com/ericproulx). +* [#1941](https://github.com/ruby-grape/grape/pull/1941): Adds frozen string literal - [@ericproulx](https://github.com/ericproulx). +* [#1940](https://github.com/ruby-grape/grape/pull/1940): Gets rid of a needless step in `HashWithIndifferentAccess` - [@dnesteryuk](https://github.com/dnesteryuk). +* [#1938](https://github.com/ruby-grape/grape/pull/1938): Adds project metadata to the gemspec - [@orien](https://github.com/orien). +* [#1920](https://github.com/ruby-grape/grape/pull/1920): Replaces Virtus with dry-types - [@dnesteryuk](https://github.com/dnesteryuk). +* [#1930](https://github.com/ruby-grape/grape/pull/1930): Moves block call to separate method so it can be spied on - [@estolfo](https://github.com/estolfo). #### Fixes diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index b6085973f..97ff3d7fb 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -262,7 +262,7 @@ def run run_validators validations, request remove_renamed_params run_filters after_validations, :after_validation - response_object = @block ? @block.call(self) : nil + response_object = execute end run_filters afters, :after @@ -335,6 +335,10 @@ def remove_renamed_params private :build_stack, :build_helpers, :remove_renamed_params + def execute + @block ? @block.call(self) : nil + end + def helpers lazy_initialize! && @helpers end From 134255931f5568d7ac546991f34453ff56b2479c Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 22 Dec 2019 13:01:53 +0200 Subject: [PATCH 151/290] careful check for empty params Integers don't respond to the `empty?` method. It caused issue with optional params as described in #1847. --- CHANGELOG.md | 1 + lib/grape/validations/attributes_iterator.rb | 4 +--- .../validations/single_attribute_iterator.rb | 13 ++++++++++-- lib/grape/validations/validators/base.rb | 10 +++++----- .../single_attribute_iterator_spec.rb | 20 +++++++++++++++---- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc21ebf1b..22b66f3f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ #### Fixes +* [#1947](https://github.com/ruby-grape/grape/pull/1947): Careful check for empty params - [@dnesteryuk](https://github.com/dnesteryuk). * [#1931](https://github.com/ruby-grape/grape/pull/1946): Fixes issue when using namespaces in `Grape::API::Instance` mounted directly - [@myxoh](https://github.com/myxoh). ### 1.2.5 (2019/12/01) diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index 40dc91edf..6c53d469a 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -31,9 +31,7 @@ def do_each(params_to_process, parent_indicies = [], &block) if @scope.type == Array next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array - inside_array = true - end - if inside_array + # fill current and parent scopes with correct array indicies parent_scope = @scope.parent parent_indicies.each do |parent_index| diff --git a/lib/grape/validations/single_attribute_iterator.rb b/lib/grape/validations/single_attribute_iterator.rb index a76cfa836..f28159896 100644 --- a/lib/grape/validations/single_attribute_iterator.rb +++ b/lib/grape/validations/single_attribute_iterator.rb @@ -5,11 +5,20 @@ module Validations class SingleAttributeIterator < AttributesIterator private - def yield_attributes(resource_params, attrs) + def yield_attributes(val, attrs) attrs.each do |attr_name| - yield resource_params, attr_name + yield val, attr_name, empty?(val) end end + + # Primitives like Integers and Booleans don't respond to +empty?+. + # It could be possible to use +blank?+ instead, but + # + # false.blank? + # => true + def empty?(val) + val.respond_to?(:empty?) ? val.empty? : val.nil? + end end end end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 22997e8bc..1d7bb90e8 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -42,12 +42,12 @@ def validate!(params) # there may be more than one error per field array_errors = [] - attributes.each do |resource_params, attr_name| - next if !@scope.required? && resource_params.empty? - next unless @scope.meets_dependency?(resource_params, params) + attributes.each do |val, attr_name, empty_val| + next if !@scope.required? && empty_val + next unless @scope.meets_dependency?(val, params) begin - if @required || resource_params.respond_to?(:key?) && resource_params.key?(attr_name) - validate_param!(attr_name, resource_params) + if @required || val.respond_to?(:key?) && val.key?(attr_name) + validate_param!(attr_name, val) end rescue Grape::Exceptions::Validation => e array_errors << e diff --git a/spec/grape/validations/single_attribute_iterator_spec.rb b/spec/grape/validations/single_attribute_iterator_spec.rb index abce54d86..2b3edbf06 100644 --- a/spec/grape/validations/single_attribute_iterator_spec.rb +++ b/spec/grape/validations/single_attribute_iterator_spec.rb @@ -6,7 +6,7 @@ describe '#each' do subject(:iterator) { described_class.new(validator, scope, params) } let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) } - let(:validator) { double(attrs: %i[first second third]) } + let(:validator) { double(attrs: %i[first second]) } context 'when params is a hash' do let(:params) do @@ -15,7 +15,7 @@ it 'yields params and every single attribute from the list' do expect { |b| iterator.each(&b) } - .to yield_successive_args([params, :first], [params, :second], [params, :third]) + .to yield_successive_args([params, :first, false], [params, :second, false]) end end @@ -26,10 +26,22 @@ it 'yields every single attribute from the list for each of the array elements' do expect { |b| iterator.each(&b) }.to yield_successive_args( - [params[0], :first], [params[0], :second], [params[0], :third], - [params[1], :first], [params[1], :second], [params[1], :third] + [params[0], :first, false], [params[0], :second, false], + [params[1], :first, false], [params[1], :second, false] ) end + + context 'empty values' do + let(:params) { [{}, '', 10] } + + it 'marks params with empty values' do + expect { |b| iterator.each(&b) }.to yield_successive_args( + [params[0], :first, true], [params[0], :second, true], + [params[1], :first, true], [params[1], :second, true], + [params[2], :first, false], [params[2], :second, false] + ) + end + end end end end From 3a57848a7eec1ecde29310c5787e7dfd98b48156 Mon Sep 17 00:00:00 2001 From: Nikita Bulai Date: Thu, 26 Dec 2019 14:53:11 +0300 Subject: [PATCH 152/290] Relax dry-types dependency version --- CHANGELOG.md | 1 + grape.gemspec | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22b66f3f1..3ac70f7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj). * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). * [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduces number of regex string allocations - [@ericproulx](https://github.com/ericproulx). * [#1942](https://github.com/ruby-grape/grape/pull/1942): Optimizes retained memory methods - [@ericproulx](https://github.com/ericproulx). diff --git a/grape.gemspec b/grape.gemspec index 3a779d3fb..6ba65bb33 100644 --- a/grape.gemspec +++ b/grape.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_runtime_dependency 'activesupport' s.add_runtime_dependency 'builder' - s.add_runtime_dependency 'dry-types', '~> 1.1.1' + s.add_runtime_dependency 'dry-types', '>= 1.1' s.add_runtime_dependency 'mustermann-grape', '~> 1.0.0' s.add_runtime_dependency 'rack', '>= 1.3.0' s.add_runtime_dependency 'rack-accept' From 84320ad4aa1083b6d2dd396d697d0426c560e2a5 Mon Sep 17 00:00:00 2001 From: Nikita Bulai Date: Fri, 27 Dec 2019 15:21:36 +0300 Subject: [PATCH 153/290] Add support for Ruby 2.7 * Add Ruby 2.7 to Travis * Fix calls with deprecation messages for kwargs --- .travis.yml | 18 +++++++++++------- CHANGELOG.md | 1 + lib/grape/api/instance.rb | 2 +- lib/grape/dsl/inside_route.rb | 6 +++--- lib/grape/dsl/parameters.rb | 4 ++-- lib/grape/dsl/request_response.rb | 4 ++-- lib/grape/dsl/routing.rb | 2 +- lib/grape/endpoint.rb | 10 +++++----- lib/grape/error_formatter.rb | 2 +- lib/grape/exceptions/base.rb | 12 ++++++------ lib/grape/formatter.rb | 6 +++--- lib/grape/middleware/error.rb | 2 +- lib/grape/middleware/formatter.rb | 4 ++-- lib/grape/parser.rb | 2 +- lib/grape/router/pattern.rb | 4 ++-- lib/grape/validations/params_scope.rb | 2 +- .../validations/validators/all_or_none.rb | 2 +- .../validations/validators/allow_blank.rb | 2 +- .../validations/validators/at_least_one_of.rb | 2 +- .../validations/validators/exactly_one_of.rb | 2 +- .../validations/validators/except_values.rb | 2 +- .../validations/validators/mutual_exclusion.rb | 2 +- lib/grape/validations/validators/presence.rb | 2 +- lib/grape/validations/validators/regexp.rb | 2 +- lib/grape/validations/validators/same_as.rb | 7 ++++--- lib/grape/validations/validators/values.rb | 6 +++--- spec/grape/api/custom_validations_spec.rb | 6 +++--- spec/grape/api_spec.rb | 2 +- spec/grape/exceptions/base_spec.rb | 2 +- spec/grape/exceptions/validation_spec.rb | 2 +- spec/grape/middleware/exception_spec.rb | 2 +- .../versioner/accept_version_header_spec.rb | 2 +- spec/grape/middleware/versioner/header_spec.rb | 2 +- spec/grape/middleware/versioner/param_spec.rb | 2 +- spec/grape/middleware/versioner/path_spec.rb | 2 +- spec/grape/parser_spec.rb | 10 +++++----- .../validations/instance_behaivour_spec.rb | 4 ++-- spec/grape/validations_spec.rb | 6 +++--- spec/support/versioned_helpers.rb | 4 ++-- 39 files changed, 81 insertions(+), 75 deletions(-) diff --git a/.travis.yml b/.travis.yml index 194f08d64..ac4a84eb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,27 +8,31 @@ gemfile: matrix: include: - - rvm: 2.6.5 + - rvm: 2.7.0 script: - bundle exec danger - - rvm: 2.6.5 + - rvm: 2.7.0 gemfile: Gemfile - - rvm: 2.6.5 + - rvm: 2.7.0 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.6.5 + - rvm: 2.7.0 gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.6.5 + - rvm: 2.7.0 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.6.5 + - rvm: 2.7.0 gemfile: gemfiles/multi_json.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_json - - rvm: 2.6.5 + - rvm: 2.7.0 gemfile: gemfiles/multi_xml.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_xml + - rvm: 2.6.5 + gemfile: Gemfile + - rvm: 2.6.5 + gemfile: gemfiles/rails_5.gemfile - rvm: 2.5.7 gemfile: Gemfile - rvm: 2.5.7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac70f7b8..792342ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1948](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj). * [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj). * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). * [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduces number of regex string allocations - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 03358fd79..84e36d467 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -234,7 +234,7 @@ def add_head_not_allowed_methods_and_options_methods end attributes = config.merge(allowed_methods: allowed_methods, allow_header: allow_header) - generate_not_allowed_method(config[:pattern], attributes) + generate_not_allowed_method(config[:pattern], **attributes) end end end diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 57fbc8b94..5943b1f73 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -30,7 +30,7 @@ def self.post_filter_methods(type) module PostBeforeFilter def declared(passed_params, options = {}, declared_params = nil) options = options.reverse_merge(include_missing: true, include_parent_namespaces: true) - declared_params ||= optioned_declared_params(options) + declared_params ||= optioned_declared_params(**options) if passed_params.is_a?(Array) declared_array(passed_params, options, declared_params) @@ -98,7 +98,7 @@ def optioned_param_key(declared_param, options) options[:stringify] ? declared_param.to_s : declared_param.to_sym end - def optioned_declared_params(options) + def optioned_declared_params(**options) declared_params = if options[:include_parent_namespaces] # Declared params including parent namespaces route_setting(:saved_declared_params).flatten | Array(route_setting(:declared_params)) @@ -387,7 +387,7 @@ def entity_class_for_obj(object, options) def entity_representation_for(entity_class, object, options) embeds = { env: env } embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION] - entity_class.represent(object, embeds.merge(options)) + entity_class.represent(object, **embeds.merge(options)) end end end diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index 7fa0e6307..bb5e4034a 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -133,7 +133,7 @@ def requires(*attrs, &block) require_required_and_optional_fields(attrs.first, opts) else validate_attributes(attrs, opts, &block) - block_given? ? new_scope(orig_attrs, &block) : push_declared_params(attrs, opts.slice(:as)) + block_given? ? new_scope(orig_attrs, &block) : push_declared_params(attrs, **opts.slice(:as)) end end @@ -159,7 +159,7 @@ def optional(*attrs, &block) else validate_attributes(attrs, opts, &block) - block_given? ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, opts.slice(:as)) + block_given? ? new_scope(orig_attrs, true, &block) : push_declared_params(attrs, **opts.slice(:as)) end end diff --git a/lib/grape/dsl/request_response.rb b/lib/grape/dsl/request_response.rb index 02587e346..cbb1db912 100644 --- a/lib/grape/dsl/request_response.rb +++ b/lib/grape/dsl/request_response.rb @@ -22,7 +22,7 @@ def format(new_format = nil) if new_format namespace_inheritable(:format, new_format.to_sym) # define the default error formatters - namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(new_format, {})) + namespace_inheritable(:default_error_formatter, Grape::ErrorFormatter.formatter_for(new_format, **{})) # define a single mime type mime_type = content_types[new_format.to_sym] raise Grape::Exceptions::MissingMimeType.new(new_format) unless mime_type @@ -45,7 +45,7 @@ def parser(content_type, new_parser) # Specify a default error formatter. def default_error_formatter(new_formatter_name = nil) if new_formatter_name - new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name, {}) + new_formatter = Grape::ErrorFormatter.formatter_for(new_formatter_name, **{}) namespace_inheritable(:default_error_formatter, new_formatter) else namespace_inheritable(:default_error_formatter) diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index e89b9ee34..ecbbb40ce 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -169,7 +169,7 @@ def namespace(space = nil, options = {}, &block) @namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {}) nest(block) do if space - namespace_stackable(:namespace, Namespace.new(space, options)) + namespace_stackable(:namespace, Namespace.new(space, **options)) end end @namespace_description = previous_namespace_description diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 97ff3d7fb..3fb417874 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -157,7 +157,7 @@ def mount_in(router) end methods.each do |method| unless route.request_method == method - route = Grape::Router::Route.new(method, route.origin, route.attributes.to_h) + route = Grape::Router::Route.new(method, route.origin, **route.attributes.to_h) end router.append(route.apply(self)) end @@ -169,8 +169,8 @@ def to_routes route_options = prepare_default_route_attributes map_routes do |method, path| path = prepare_path(path) - params = merge_route_options(route_options.merge(suffix: path.suffix)) - route = Router::Route.new(method, path.path, params) + params = merge_route_options(**route_options.merge(suffix: path.suffix)) + route = Router::Route.new(method, path.path, **params) route.apply(self) end.flatten end @@ -359,7 +359,7 @@ def lazy_initialize! def run_validators(validator_factories, request) validation_errors = [] - validators = validator_factories.map { |options| Grape::Validations::ValidatorFactory.create_validator(options) } + validators = validator_factories.map { |options| Grape::Validations::ValidatorFactory.create_validator(**options) } ActiveSupport::Notifications.instrument('endpoint_run_validators.grape', endpoint: self, validators: validators, request: request) do validators.each do |validator| @@ -375,7 +375,7 @@ def run_validators(validator_factories, request) end end - validation_errors.any? && raise(Grape::Exceptions::ValidationErrors, errors: validation_errors, headers: header) + validation_errors.any? && raise(Grape::Exceptions::ValidationErrors.new(errors: validation_errors, headers: header)) end def run_filters(filters, type = :other) diff --git a/lib/grape/error_formatter.rb b/lib/grape/error_formatter.rb index 535038492..4d76fe296 100644 --- a/lib/grape/error_formatter.rb +++ b/lib/grape/error_formatter.rb @@ -15,7 +15,7 @@ def builtin_formatters } end - def formatters(options) + def formatters(**options) builtin_formatters.merge(default_elements).merge!(options[:error_formatters] || {}) end diff --git a/lib/grape/exceptions/base.rb b/lib/grape/exceptions/base.rb index 25d3f08c3..7f60d90ea 100644 --- a/lib/grape/exceptions/base.rb +++ b/lib/grape/exceptions/base.rb @@ -39,16 +39,16 @@ def compose_message(key, **attributes) end end - def problem(key, attributes) - translate_message("#{key}.problem".to_sym, attributes) + def problem(key, **attributes) + translate_message("#{key}.problem".to_sym, **attributes) end - def summary(key, attributes) - translate_message("#{key}.summary".to_sym, attributes) + def summary(key, **attributes) + translate_message("#{key}.summary".to_sym, **attributes) end - def resolution(key, attributes) - translate_message("#{key}.resolution".to_sym, attributes) + def resolution(key, **attributes) + translate_message("#{key}.resolution".to_sym, **attributes) end def translate_attributes(keys, **options) diff --git a/lib/grape/formatter.rb b/lib/grape/formatter.rb index a9cebaef2..4a84f0e2b 100644 --- a/lib/grape/formatter.rb +++ b/lib/grape/formatter.rb @@ -5,7 +5,7 @@ module Formatter extend Util::Registrable class << self - def builtin_formmaters + def builtin_formatters @builtin_formatters ||= { json: Grape::Formatter::Json, jsonapi: Grape::Formatter::Json, @@ -15,8 +15,8 @@ def builtin_formmaters } end - def formatters(options) - builtin_formmaters.merge(default_elements).merge!(options[:formatters] || {}) + def formatters(**options) + builtin_formatters.merge(default_elements).merge!(options[:formatters] || {}) end def formatter_for(api_format, **options) diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index d6591964d..6d816f14f 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -79,7 +79,7 @@ def rack_response(message, status = options[:default_status], headers = { Grape: def format_message(message, backtrace, original_exception = nil) format = env[Grape::Env::API_FORMAT] || options[:format] - formatter = Grape::ErrorFormatter.formatter_for(format, options) + formatter = Grape::ErrorFormatter.formatter_for(format, **options) throw :error, status: 406, message: "The requested format '#{format}' is not supported.", diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 3667451cd..7ea7e57af 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -54,7 +54,7 @@ def build_formatted_response(status, headers, bodies) def fetch_formatter(headers, options) api_format = mime_types[headers[Grape::Http::Headers::CONTENT_TYPE]] || env[Grape::Env::API_FORMAT] - Grape::Formatter.formatter_for(api_format, options) + Grape::Formatter.formatter_for(api_format, **options) end # Set the content type header for the API format if it is not already present. @@ -99,7 +99,7 @@ def read_rack_input(body) unless content_type_for(fmt) throw :error, status: 415, message: "The provided content-type '#{request.media_type}' is not supported." end - parser = Grape::Parser.parser_for fmt, options + parser = Grape::Parser.parser_for fmt, **options if parser begin body = (env[Grape::Env::API_REQUEST_BODY] = parser.call(body, env)) diff --git a/lib/grape/parser.rb b/lib/grape/parser.rb index e7bf622fc..3676a45a7 100644 --- a/lib/grape/parser.rb +++ b/lib/grape/parser.rb @@ -13,7 +13,7 @@ def builtin_parsers } end - def parsers(options) + def parsers(**options) builtin_parsers.merge(default_elements).merge!(options[:parsers] || {}) end diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index 9da362f5c..258945c9d 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -19,8 +19,8 @@ class Pattern def initialize(pattern, **options) @origin = pattern @path = build_path(pattern, **options) - @capture = extract_capture(options) - @pattern = Mustermann.new(@path, pattern_options) + @capture = extract_capture(**options) + @pattern = Mustermann.new(@path, **pattern_options) @regexp = to_regexp end diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 9c9fbbe74..0a5ced10b 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -123,7 +123,7 @@ def required? # @param attrs [Array] (see Grape::DSL::Parameters#requires) def push_declared_params(attrs, **opts) if lateral? - @parent.push_declared_params(attrs, opts) + @parent.push_declared_params(attrs, **opts) else if opts && opts[:as] @api.route_setting(:renamed_params, @api.route_setting(:renamed_params) || []) diff --git a/lib/grape/validations/validators/all_or_none.rb b/lib/grape/validations/validators/all_or_none.rb index e807d018b..186361f0d 100644 --- a/lib/grape/validations/validators/all_or_none.rb +++ b/lib/grape/validations/validators/all_or_none.rb @@ -8,7 +8,7 @@ class AllOrNoneOfValidator < MultipleParamsBase def validate_params!(params) keys = keys_in_common(params) return if keys.empty? || keys.length == all_keys.length - raise Grape::Exceptions::Validation, params: all_keys, message: message(:all_or_none) + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:all_or_none)) end end end diff --git a/lib/grape/validations/validators/allow_blank.rb b/lib/grape/validations/validators/allow_blank.rb index 8230214b8..e212c273c 100644 --- a/lib/grape/validations/validators/allow_blank.rb +++ b/lib/grape/validations/validators/allow_blank.rb @@ -11,7 +11,7 @@ def validate_param!(attr_name, params) return if value == false || value.present? - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:blank) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:blank)) end end end diff --git a/lib/grape/validations/validators/at_least_one_of.rb b/lib/grape/validations/validators/at_least_one_of.rb index ba2f17928..001c784dd 100644 --- a/lib/grape/validations/validators/at_least_one_of.rb +++ b/lib/grape/validations/validators/at_least_one_of.rb @@ -7,7 +7,7 @@ module Validations class AtLeastOneOfValidator < MultipleParamsBase def validate_params!(params) return unless keys_in_common(params).empty? - raise Grape::Exceptions::Validation, params: all_keys, message: message(:at_least_one) + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:at_least_one)) end end end diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index 4a22970f1..8aa616ea6 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -7,7 +7,7 @@ module Validations class ExactlyOneOfValidator < MultipleParamsBase def validate_params!(params) return if keys_in_common(params).length == 1 - raise Grape::Exceptions::Validation, params: all_keys, message: message(:exactly_one) + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) end end end diff --git a/lib/grape/validations/validators/except_values.rb b/lib/grape/validations/validators/except_values.rb index 9c5d6c040..4ef94e97a 100644 --- a/lib/grape/validations/validators/except_values.rb +++ b/lib/grape/validations/validators/except_values.rb @@ -15,7 +15,7 @@ def validate_param!(attr_name, params) return if excepts.nil? param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name]) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:except_values) if param_array.any? { |param| excepts.include?(param) } + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:except_values)) if param_array.any? { |param| excepts.include?(param) } end end end diff --git a/lib/grape/validations/validators/mutual_exclusion.rb b/lib/grape/validations/validators/mutual_exclusion.rb index a3b21488d..bcd25bcae 100644 --- a/lib/grape/validations/validators/mutual_exclusion.rb +++ b/lib/grape/validations/validators/mutual_exclusion.rb @@ -8,7 +8,7 @@ class MutualExclusionValidator < MultipleParamsBase def validate_params!(params) keys = keys_in_common(params) return if keys.length <= 1 - raise Grape::Exceptions::Validation, params: keys, message: message(:mutual_exclusion) + raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion)) end end end diff --git a/lib/grape/validations/validators/presence.rb b/lib/grape/validations/validators/presence.rb index 3d32c7d7c..92ec570f4 100644 --- a/lib/grape/validations/validators/presence.rb +++ b/lib/grape/validations/validators/presence.rb @@ -5,7 +5,7 @@ module Validations class PresenceValidator < Base def validate_param!(attr_name, params) return if params.respond_to?(:key?) && params.key?(attr_name) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:presence) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:presence)) end end end diff --git a/lib/grape/validations/validators/regexp.rb b/lib/grape/validations/validators/regexp.rb index a6bbd7494..ea220b81c 100644 --- a/lib/grape/validations/validators/regexp.rb +++ b/lib/grape/validations/validators/regexp.rb @@ -6,7 +6,7 @@ class RegexpValidator < Base def validate_param!(attr_name, params) return unless params.respond_to?(:key?) && params.key?(attr_name) return if Array.wrap(params[attr_name]).all? { |param| param.nil? || (param.to_s =~ (options_key?(:value) ? @option[:value] : @option)) } - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:regexp) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp)) end end end diff --git a/lib/grape/validations/validators/same_as.rb b/lib/grape/validations/validators/same_as.rb index b7f63b8c5..087150f16 100644 --- a/lib/grape/validations/validators/same_as.rb +++ b/lib/grape/validations/validators/same_as.rb @@ -6,9 +6,10 @@ class SameAsValidator < Base def validate_param!(attr_name, params) confirmation = options_key?(:value) ? @option[:value] : @option return if params[attr_name] == params[confirmation] - raise Grape::Exceptions::Validation, - params: [@scope.full_name(attr_name)], - message: build_message + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: build_message + ) end private diff --git a/lib/grape/validations/validators/values.rb b/lib/grape/validations/validators/values.rb index a7486acb1..7fe12bed1 100644 --- a/lib/grape/validations/validators/values.rb +++ b/lib/grape/validations/validators/values.rb @@ -30,13 +30,13 @@ def validate_param!(attr_name, params) param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name]) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: except_message \ + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: except_message) \ unless check_excepts(param_array) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values) \ + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:values)) \ unless check_values(param_array, attr_name) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:values) \ + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:values)) \ if @proc && !param_array.all? { |param| @proc.call(param) } end diff --git a/spec/grape/api/custom_validations_spec.rb b/spec/grape/api/custom_validations_spec.rb index bb12b877f..f69ec5199 100644 --- a/spec/grape/api/custom_validations_spec.rb +++ b/spec/grape/api/custom_validations_spec.rb @@ -10,7 +10,7 @@ class DefaultLength < Grape::Validations::Base def validate_param!(attr_name, params) @option = params[:max].to_i if params.key?(:max) return if params[attr_name].length <= @option - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long" + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "must be at the most #{@option} characters long") end end end @@ -89,7 +89,7 @@ def app module CustomValidationsSpec class WithMessageKey < Grape::Validations::PresenceValidator def validate_param!(attr_name, _params) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: :presence + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: :presence) end end end @@ -128,7 +128,7 @@ def validate(request) return unless @option # check if user is admin or not # as an example get a token from request and check if it's admin or not - raise Grape::Exceptions::Validation, params: @attrs, message: 'Can not set Admin only field.' unless request.headers['X-Access-Token'] == 'admin' + raise Grape::Exceptions::Validation.new(params: @attrs, message: 'Can not set Admin only field.') unless request.headers['X-Access-Token'] == 'admin' end end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 68888f6ee..b506ac0ee 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1960,7 +1960,7 @@ class CustomError < Grape::Exceptions::Base; end rack_response('New Error', e.status) end subject.get '/custom_error' do - raise ApiSpec::CustomError, status: 400, message: 'Custom Error' + raise ApiSpec::CustomError.new(status: 400, message: 'Custom Error') end get '/custom_error' diff --git a/spec/grape/exceptions/base_spec.rb b/spec/grape/exceptions/base_spec.rb index aaf898f55..db970a74d 100644 --- a/spec/grape/exceptions/base_spec.rb +++ b/spec/grape/exceptions/base_spec.rb @@ -4,7 +4,7 @@ describe Grape::Exceptions::Base do describe '#compose_message' do - subject { described_class.new.send(:compose_message, key, attributes) } + subject { described_class.new.send(:compose_message, key, **attributes) } let(:key) { :invalid_formatter } let(:attributes) { { klass: String, to_format: 'xml' } } diff --git a/spec/grape/exceptions/validation_spec.rb b/spec/grape/exceptions/validation_spec.rb index 2eb4d3433..5486cbf64 100644 --- a/spec/grape/exceptions/validation_spec.rb +++ b/spec/grape/exceptions/validation_spec.rb @@ -4,7 +4,7 @@ describe Grape::Exceptions::Validation do it 'fails when params are missing' do - expect { Grape::Exceptions::Validation.new(message: 'presence') }.to raise_error(ArgumentError, 'missing keyword: params') + expect { Grape::Exceptions::Validation.new(message: 'presence') }.to raise_error(ArgumentError, /missing keyword:.+?params/) end context 'when message is a symbol' do it 'stores message_key' do diff --git a/spec/grape/middleware/exception_spec.rb b/spec/grape/middleware/exception_spec.rb index 708c1885d..11aa8e5aa 100644 --- a/spec/grape/middleware/exception_spec.rb +++ b/spec/grape/middleware/exception_spec.rb @@ -55,7 +55,7 @@ class CustomError < Grape::Exceptions::Base class CustomErrorApp class << self def call(_env) - raise CustomError, status: 400, message: 'failed validation' + raise CustomError.new(status: 400, message: 'failed validation') end end end diff --git a/spec/grape/middleware/versioner/accept_version_header_spec.rb b/spec/grape/middleware/versioner/accept_version_header_spec.rb index 03a204b56..f13d44175 100644 --- a/spec/grape/middleware/versioner/accept_version_header_spec.rb +++ b/spec/grape/middleware/versioner/accept_version_header_spec.rb @@ -4,7 +4,7 @@ describe Grape::Middleware::Versioner::AcceptVersionHeader do let(:app) { ->(env) { [200, env, env] } } - subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, @options || {}) } + subject { Grape::Middleware::Versioner::AcceptVersionHeader.new(app, **(@options || {})) } before do @options = { diff --git a/spec/grape/middleware/versioner/header_spec.rb b/spec/grape/middleware/versioner/header_spec.rb index fd85061ac..b2349a712 100644 --- a/spec/grape/middleware/versioner/header_spec.rb +++ b/spec/grape/middleware/versioner/header_spec.rb @@ -4,7 +4,7 @@ describe Grape::Middleware::Versioner::Header do let(:app) { ->(env) { [200, env, env] } } - subject { Grape::Middleware::Versioner::Header.new(app, @options || {}) } + subject { Grape::Middleware::Versioner::Header.new(app, **(@options || {})) } before do @options = { diff --git a/spec/grape/middleware/versioner/param_spec.rb b/spec/grape/middleware/versioner/param_spec.rb index 714b07a98..328696128 100644 --- a/spec/grape/middleware/versioner/param_spec.rb +++ b/spec/grape/middleware/versioner/param_spec.rb @@ -5,7 +5,7 @@ describe Grape::Middleware::Versioner::Param do let(:app) { ->(env) { [200, env, env['api.version']] } } let(:options) { {} } - subject { Grape::Middleware::Versioner::Param.new(app, options) } + subject { Grape::Middleware::Versioner::Param.new(app, **options) } it 'sets the API version based on the default param (apiver)' do env = Rack::MockRequest.env_for('/awesome', params: { 'apiver' => 'v1' }) diff --git a/spec/grape/middleware/versioner/path_spec.rb b/spec/grape/middleware/versioner/path_spec.rb index 33ec99aa1..e703931f3 100644 --- a/spec/grape/middleware/versioner/path_spec.rb +++ b/spec/grape/middleware/versioner/path_spec.rb @@ -5,7 +5,7 @@ describe Grape::Middleware::Versioner::Path do let(:app) { ->(env) { [200, env, env['api.version']] } } let(:options) { {} } - subject { Grape::Middleware::Versioner::Path.new(app, options) } + subject { Grape::Middleware::Versioner::Path.new(app, **options) } it 'sets the API version based on the first path' do expect(subject.call('PATH_INFO' => '/v1/awesome').last).to eq('v1') diff --git a/spec/grape/parser_spec.rb b/spec/grape/parser_spec.rb index 38ffbc03d..ace8954fa 100644 --- a/spec/grape/parser_spec.rb +++ b/spec/grape/parser_spec.rb @@ -17,11 +17,11 @@ describe '.parsers' do it 'returns an instance of Hash' do - expect(subject.parsers({})).to be_an_instance_of(Hash) + expect(subject.parsers(**{})).to be_an_instance_of(Hash) end it 'includes built-in parsers' do - expect(subject.parsers({})).to include(subject.builtin_parsers) + expect(subject.parsers(**{})).to include(subject.builtin_parsers) end context 'with :parsers option' do @@ -35,7 +35,7 @@ let(:added_parser) { Class.new } before { subject.register :added, added_parser } it 'includes added parser' do - expect(subject.parsers({})).to include(added: added_parser) + expect(subject.parsers(**{})).to include(added: added_parser) end end end @@ -44,8 +44,8 @@ let(:options) { {} } it 'calls .parsers' do - expect(subject).to receive(:parsers).with(options).and_return(subject.builtin_parsers) - subject.parser_for(:json, options) + expect(subject).to receive(:parsers).with(any_args).and_return(subject.builtin_parsers) + subject.parser_for(:json, **options) end it 'returns parser correctly' do diff --git a/spec/grape/validations/instance_behaivour_spec.rb b/spec/grape/validations/instance_behaivour_spec.rb index fb10912ee..9db070d95 100644 --- a/spec/grape/validations/instance_behaivour_spec.rb +++ b/spec/grape/validations/instance_behaivour_spec.rb @@ -7,8 +7,8 @@ Class.new(Grape::Validations::Base) do def validate_param!(_attr_name, _params) if @instance_variable - raise Grape::Exceptions::Validation, params: ['params'], - message: 'This should never happen' + raise Grape::Exceptions::Validation.new(params: ['params'], + message: 'This should never happen') end @instance_variable = true end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index c704d8040..3e8df50ff 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -494,7 +494,7 @@ module DateRangeValidations class DateRangeValidator < Grape::Validations::Base def validate_param!(attr_name, params) return if params[attr_name][:from] <= params[attr_name][:to] - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'" + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: "'from' must be lower or equal to 'to'") end end end @@ -909,7 +909,7 @@ module CustomValidations class Customvalidator < Grape::Validations::Base def validate_param!(attr_name, params) return if params[attr_name] == 'im custom' - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: 'is not custom!' + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: 'is not custom!') end end end @@ -1057,7 +1057,7 @@ module CustomValidations class CustomvalidatorWithOptions < Grape::Validations::Base def validate_param!(attr_name, params) return if params[attr_name] == @option[:text] - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message) end end end diff --git a/spec/support/versioned_helpers.rb b/spec/support/versioned_helpers.rb index acd48ccf4..f3055c6e5 100644 --- a/spec/support/versioned_helpers.rb +++ b/spec/support/versioned_helpers.rb @@ -21,7 +21,7 @@ def versioned_path(options = {}) end end - def versioned_headers(options) + def versioned_headers(**options) case options[:using] when :path {} # no-op @@ -45,7 +45,7 @@ def versioned_headers(options) def versioned_get(path, version_name, version_options = {}) path = versioned_path(version_options.merge(version: version_name, path: path)) - headers = versioned_headers(version_options.merge(version: version_name)) + headers = versioned_headers(**version_options.merge(version: version_name)) params = {} if version_options[:using] == :param params = { version_options[:parameter] => version_name } From bdd03bef1dc36c8e1740aceb27d3cef75f27df96 Mon Sep 17 00:00:00 2001 From: Nikita Bulai Date: Tue, 31 Dec 2019 13:54:49 +0300 Subject: [PATCH 154/290] Small Ruby 2.7 + changelog fixes --- CHANGELOG.md | 2 +- lib/grape/exceptions/validation.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792342ea7..95bbafc63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ #### Features * Your contribution here. -* [#1948](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj). +* [#1949](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj). * [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj). * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). * [#1943](https://github.com/ruby-grape/grape/pull/1943): Reduces number of regex string allocations - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape/exceptions/validation.rb b/lib/grape/exceptions/validation.rb index 8b519d9c2..bfd1dee2d 100644 --- a/lib/grape/exceptions/validation.rb +++ b/lib/grape/exceptions/validation.rb @@ -14,7 +14,7 @@ def initialize(params:, message: nil, **args) @message_key = message if message.is_a?(Symbol) args[:message] = translate_message(message) end - super(args) + super(**args) end # remove all the unnecessary stuff from Grape::Exceptions::Base like status From 0cfc310d8d6101d54a6128bc71ed01c9c8262b4f Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 31 Dec 2019 11:46:23 +0200 Subject: [PATCH 155/290] consider the allow_blank option in the values validator When `allow_blank` is true, the values validator shouldn't raise `Grape::Exceptions::Validation` in case of a blank value. --- CHANGELOG.md | 1 + lib/grape/validations/params_scope.rb | 15 ++++++++++++--- lib/grape/validations/validators/base.rb | 1 + lib/grape/validations/validators/values.rb | 10 ++++++++-- spec/grape/validations/validators/values_spec.rb | 13 +++++++++++++ 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792342ea7..a80d4ad86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ #### Fixes +* [#1950](https://github.com/ruby-grape/grape/pull/1950): Consider the allow_blank option in the values validator - [@dnesteryuk](https://github.com/dnesteryuk). * [#1947](https://github.com/ruby-grape/grape/pull/1947): Careful check for empty params - [@dnesteryuk](https://github.com/dnesteryuk). * [#1931](https://github.com/ruby-grape/grape/pull/1946): Fixes issue when using namespaces in `Grape::API::Instance` mounted directly - [@myxoh](https://github.com/myxoh). diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 0a5ced10b..0cf8d2ef7 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -282,9 +282,7 @@ def validates(attrs, validations) full_attrs = attrs.collect { |name| { name: name, full_name: full_name(name) } } @api.document_attribute(full_attrs, doc_attrs) - # slice out fail_fast attribute - opts = {} - opts[:fail_fast] = validations.delete(:fail_fast) || false + opts = derive_validator_options(validations) # Validate for presence before any other validators if validations.key?(:presence) && validations[:presence] @@ -451,6 +449,17 @@ def options_key?(type, key, validations) def all_element_blank?(parameters) params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?) end + + # Validators don't have access to each other and they don't need, however, + # some validators might influence others, so their options should be shared + def derive_validator_options(validations) + allow_blank = validations[:allow_blank] + + { + allow_blank: allow_blank.is_a?(Hash) ? allow_blank[:value] : allow_blank, + fail_fast: validations.delete(:fail_fast) || false + } + end end end end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 1d7bb90e8..034a66e5a 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -19,6 +19,7 @@ def initialize(attrs, options, required, scope, opts = {}) @required = required @scope = scope @fail_fast = opts[:fail_fast] || false + @allow_blank = opts[:allow_blank] || false end # Validates a given request. diff --git a/lib/grape/validations/validators/values.rb b/lib/grape/validations/validators/values.rb index 7fe12bed1..0f01af1e1 100644 --- a/lib/grape/validations/validators/values.rb +++ b/lib/grape/validations/validators/values.rb @@ -26,9 +26,15 @@ def initialize(attrs, options, required, scope, opts = {}) def validate_param!(attr_name, params) return unless params.is_a?(Hash) - return unless params[attr_name] || required_for_root_scope? - param_array = params[attr_name].nil? ? [nil] : Array.wrap(params[attr_name]) + val = params[attr_name] + + return unless val || required_for_root_scope? + + # don't forget that +false.blank?+ is true + return if val != false && val.blank? && @allow_blank + + param_array = val.nil? ? [nil] : Array.wrap(val) raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: except_message) \ unless check_excepts(param_array) diff --git a/spec/grape/validations/validators/values_spec.rb b/spec/grape/validations/validators/values_spec.rb index ee6ec04e8..81cfe6000 100644 --- a/spec/grape/validations/validators/values_spec.rb +++ b/spec/grape/validations/validators/values_spec.rb @@ -224,6 +224,11 @@ class API < Grape::API requires :type, values: { proc: ->(v) { ValuesModel.values.include? v }, message: 'failed check' } end get '/proc/message' + + params do + optional :name, type: String, values: %w[a b], allow_blank: true + end + get '/allow_blank' end end end @@ -464,6 +469,14 @@ def app end.to raise_error Grape::Exceptions::IncompatibleOptionValues end + it 'allows a blank value when the allow_blank option is true' do + get 'allow_blank', name: nil + expect(last_response.status).to eq(200) + + get 'allow_blank', name: '' + expect(last_response.status).to eq(200) + end + context 'with a lambda values' do subject do Class.new(Grape::API) do From 9f786adbffa25574916e5d5504dacb4f5e79c6bb Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Mon, 6 Jan 2020 06:22:03 +0200 Subject: [PATCH 156/290] delete a reversible stackable values class (#1953) * delete a reversible stackable values class The reversible stackable values object was initialized for every endpoint, however, it is only needed for keeping rescue handlers. The idea is simple, handlers defined "closer" to an endpoint have higher priority. That test https://github.com/ruby-grape/grape/blob/master/spec/grape/api_spec.rb#L3215-L3232 demonstrates how it works. In our project rescue handlers are defined at the top level, so almost every endpoint keeps the unused object. The mentioned behavior is easy to achieve with the stackable values object and the `reverse_each` method. Thus, endpoints keeps less objects and have less code to be maintained. Besides that, there are a few other simple performance optimizations. --- CHANGELOG.md | 1 + lib/grape.rb | 1 - lib/grape/dsl/request_response.rb | 2 +- lib/grape/dsl/settings.rb | 15 +- lib/grape/endpoint.rb | 20 +-- lib/grape/util/inheritable_setting.rb | 8 +- lib/grape/util/reverse_stackable_values.rb | 18 --- lib/grape/util/stackable_values.rb | 1 + lib/grape/validations/attributes_iterator.rb | 3 +- lib/grape/validations/params_scope.rb | 10 +- lib/grape/validations/types/build_coercer.rb | 6 +- spec/grape/api/instance_spec.rb | 6 + spec/grape/dsl/request_response_spec.rb | 10 +- spec/grape/util/inheritable_setting_spec.rb | 23 --- .../util/reverse_stackable_values_spec.rb | 133 ------------------ spec/grape/util/stackable_values_spec.rb | 2 +- 16 files changed, 40 insertions(+), 219 deletions(-) delete mode 100644 lib/grape/util/reverse_stackable_values.rb delete mode 100644 spec/grape/util/reverse_stackable_values_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index e3527613c..47151226b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#1953](https://github.com/ruby-grape/grape/pull/1953): Delete a reversible stackable values class - [@dnesteryuk](https://github.com/dnesteryuk). * [#1949](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj). * [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj). * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape.rb b/lib/grape.rb index 388fb54bd..8d3459481 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -140,7 +140,6 @@ module Util eager_autoload do autoload :InheritableValues autoload :StackableValues - autoload :ReverseStackableValues autoload :InheritableSetting autoload :StrictHashConfiguration autoload :Registrable diff --git a/lib/grape/dsl/request_response.rb b/lib/grape/dsl/request_response.rb index cbb1db912..cab80eea9 100644 --- a/lib/grape/dsl/request_response.rb +++ b/lib/grape/dsl/request_response.rb @@ -127,7 +127,7 @@ def rescue_from(*args, &block) :base_only_rescue_handlers end - namespace_reverse_stackable handler_type, Hash[args.map { |arg| [arg, handler] }] + namespace_stackable handler_type, Hash[args.map { |arg| [arg, handler] }] end namespace_stackable(:rescue_options, options) diff --git a/lib/grape/dsl/settings.rb b/lib/grape/dsl/settings.rb index eebd12750..086b238d7 100644 --- a/lib/grape/dsl/settings.rb +++ b/lib/grape/dsl/settings.rb @@ -96,10 +96,6 @@ def namespace_stackable(key, value = nil) get_or_set :namespace_stackable, key, value end - def namespace_reverse_stackable(key, value = nil) - get_or_set :namespace_reverse_stackable, key, value - end - def namespace_stackable_with_hash(key) settings = get_or_set :namespace_stackable, key, nil return if settings.blank? @@ -107,10 +103,11 @@ def namespace_stackable_with_hash(key) end def namespace_reverse_stackable_with_hash(key) - settings = get_or_set :namespace_reverse_stackable, key, nil + settings = get_or_set :namespace_stackable, key, nil return if settings.blank? + result = {} - settings.each do |setting| + settings.reverse_each do |setting| setting.each do |field, value| result[field] ||= value end @@ -171,7 +168,11 @@ def within_namespace(&_block) # the superclass's :inheritable_setting. def build_top_level_setting Grape::Util::InheritableSetting.new.tap do |setting| - if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API + # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to + # +inheritable_setting+, however, it doesn't contain any user-defined settings. + # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+ + # in the chain for every endpoint. + if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance setting.inherit_from superclass.inheritable_setting end end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 3fb417874..6b51a622f 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -386,24 +386,8 @@ def run_filters(filters, type = :other) extend post_extension if post_extension end - def befores - namespace_stackable(:befores) || [] - end - - def before_validations - namespace_stackable(:before_validations) || [] - end - - def after_validations - namespace_stackable(:after_validations) || [] - end - - def afters - namespace_stackable(:afters) || [] - end - - def finallies - namespace_stackable(:finallies) || [] + %i[befores before_validations after_validations afters finallies].each do |meth_id| + define_method(meth_id) { namespace_stackable(meth_id) } end def validations diff --git a/lib/grape/util/inheritable_setting.rb b/lib/grape/util/inheritable_setting.rb index 2a6024bca..befc6bd68 100644 --- a/lib/grape/util/inheritable_setting.rb +++ b/lib/grape/util/inheritable_setting.rb @@ -6,7 +6,7 @@ module Util # and inheritable values (see InheritableValues and StackableValues). class InheritableSetting attr_accessor :route, :api_class, :namespace - attr_accessor :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable + attr_accessor :namespace_inheritable, :namespace_stackable attr_accessor :parent, :point_in_time_copies # Retrieve global settings. @@ -31,7 +31,6 @@ def initialize # used with a mount, or should every API::Class be a separate namespace by default? self.namespace_inheritable = InheritableValues.new self.namespace_stackable = StackableValues.new - self.namespace_reverse_stackable = ReverseStackableValues.new self.point_in_time_copies = [] @@ -54,7 +53,6 @@ def inherit_from(parent) namespace_inheritable.inherited_values = parent.namespace_inheritable namespace_stackable.inherited_values = parent.namespace_stackable - namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable self.route = parent.route.merge(route) point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent } @@ -72,7 +70,6 @@ def point_in_time_copy new_setting.namespace = namespace.clone new_setting.namespace_inheritable = namespace_inheritable.clone new_setting.namespace_stackable = namespace_stackable.clone - new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone new_setting.route = route.clone new_setting.api_class = api_class @@ -93,8 +90,7 @@ def to_hash route: route.clone, namespace: namespace.to_hash, namespace_inheritable: namespace_inheritable.to_hash, - namespace_stackable: namespace_stackable.to_hash, - namespace_reverse_stackable: namespace_reverse_stackable.to_hash + namespace_stackable: namespace_stackable.to_hash } end end diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb deleted file mode 100644 index d8e8f160c..000000000 --- a/lib/grape/util/reverse_stackable_values.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -require_relative 'stackable_values' - -module Grape - module Util - class ReverseStackableValues < StackableValues - protected - - def concat_values(inherited_value, new_value) - [].tap do |value| - value.concat(new_value) - value.concat(inherited_value) - end - end - end - end -end diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 324aa19db..79d3f3777 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -13,6 +13,7 @@ def initialize(*_args) @frozen_values = {} end + # Even if there is no value, an empty array will be returned. def [](name) return @frozen_values[name] if @frozen_values.key? name diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index 6c53d469a..bfa69cfc6 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -11,6 +11,7 @@ def initialize(validator, scope, params) @scope = scope @attrs = validator.attrs @original_params = scope.params(params) + @array_given = @original_params.is_a?(Array) @params = Array.wrap(@original_params) end @@ -30,7 +31,7 @@ def do_each(params_to_process, parent_indicies = [], &block) end if @scope.type == Array - next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array + next unless @array_given # do not validate content of array if it isn't array # fill current and parent scopes with correct array indicies parent_scope = @scope.parent diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 0cf8d2ef7..c8b1527c0 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -45,8 +45,10 @@ def configuration # @return [Boolean] whether or not this entire scope needs to be # validated def should_validate?(parameters) - return false if @optional && (params(parameters).blank? || all_element_blank?(parameters)) - return false unless meets_dependency?(params(parameters), parameters) + scoped_params = params(parameters) + + return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params)) + return false unless meets_dependency?(scoped_params, parameters) return true if parent.nil? parent.should_validate?(parameters) end @@ -446,8 +448,8 @@ def options_key?(type, key, validations) validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil? end - def all_element_blank?(parameters) - params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?) + def all_element_blank?(scoped_params) + scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?) end # Validators don't have access to each other and they don't need, however, diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index fc0762121..fc07ad4b4 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -83,7 +83,11 @@ def self.cache_instance(type, method, strict, &_block) end def self.cache_key(type, method, strict) - [type, method, strict].compact.map(&:to_s).join('_') + [type, method, strict].each_with_object(+'') do |val, memo| + next if val.nil? + + memo << '_' << val.to_s + end end instance_variable_set(:@__cache, {}) diff --git a/spec/grape/api/instance_spec.rb b/spec/grape/api/instance_spec.rb index 6de77c51e..fa5c949e5 100644 --- a/spec/grape/api/instance_spec.rb +++ b/spec/grape/api/instance_spec.rb @@ -45,4 +45,10 @@ def app expect(last_response.body).to eq 'success' end end + + context 'top level setting' do + it 'does not inherit settings from the superclass (Grape::API::Instance)' do + expect(an_instance.top_level_setting.parent).to be_nil + end + end end diff --git a/spec/grape/dsl/request_response_spec.rb b/spec/grape/dsl/request_response_spec.rb index 00d9e3a6a..515e48cb0 100644 --- a/spec/grape/dsl/request_response_spec.rb +++ b/spec/grape/dsl/request_response_spec.rb @@ -161,34 +161,34 @@ def self.imbue(key, value) describe 'list of exceptions is passed' do it 'sets hash of exceptions as rescue handlers' do - expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => nil) + expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => nil) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError end it 'rescues only base handlers if rescue_subclasses: false option is passed' do - expect(subject).to receive(:namespace_reverse_stackable).with(:base_only_rescue_handlers, StandardError => nil) + expect(subject).to receive(:namespace_stackable).with(:base_only_rescue_handlers, StandardError => nil) expect(subject).to receive(:namespace_stackable).with(:rescue_options, rescue_subclasses: false) subject.rescue_from StandardError, rescue_subclasses: false end it 'sets given proc as rescue handler for each key in hash' do rescue_handler_proc = proc {} - expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) + expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError, rescue_handler_proc end it 'sets given block as rescue handler for each key in hash' do rescue_handler_proc = proc {} - expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) + expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError, &rescue_handler_proc end it 'sets a rescue handler declared through :with option for each key in hash' do with_block = -> { 'hello' } - expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc)) + expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc)) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError, with: with_block end diff --git a/spec/grape/util/inheritable_setting_spec.rb b/spec/grape/util/inheritable_setting_spec.rb index 0e0b672e7..6b3b44ff0 100644 --- a/spec/grape/util/inheritable_setting_spec.rb +++ b/spec/grape/util/inheritable_setting_spec.rb @@ -14,7 +14,6 @@ module Util settings.namespace[:namespace_thing] = :namespace_foo_bar settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar - settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar settings.route[:route_thing] = :route_foo_bar end end @@ -24,7 +23,6 @@ module Util settings.namespace[:namespace_thing] = :namespace_foo_bar_other settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other - settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar_other settings.route[:route_thing] = :route_foo_bar_other end end @@ -122,16 +120,6 @@ module Util end end - describe '#namespace_reverse_stackable' do - it 'works with reverse stackable values' do - expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar] - - subject.inherit_from other_parent - - expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar_other] - end - end - describe '#route' do it 'sets a value until the next route' do subject.route[:some_thing] = :foo_bar @@ -198,14 +186,6 @@ module Util expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar] end - it 'decouples namespace reverse stackable values' do - expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar] - - subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :other_thing - expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq %i[other_thing namespace_reverse_stackable_foo_bar] - expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar] - end - it 'decouples route values' do expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar @@ -224,7 +204,6 @@ module Util subject.namespace[:namespace_thing] = :namespace_foo_bar subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar] - subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = [:namespace_reverse_stackable_foo_bar] subject.route[:route_thing] = :route_foo_bar expect(subject.to_hash).to include(global: { global_thing: :global_foo_bar }) @@ -233,8 +212,6 @@ module Util namespace_inheritable_thing: :namespace_inheritable_foo_bar }) expect(subject.to_hash).to include(namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] }) - expect(subject.to_hash).to include(namespace_reverse_stackable: - { namespace_reverse_stackable_thing: [[:namespace_reverse_stackable_foo_bar], :namespace_reverse_stackable_foo_bar] }) expect(subject.to_hash).to include(route: { route_thing: :route_foo_bar }) end end diff --git a/spec/grape/util/reverse_stackable_values_spec.rb b/spec/grape/util/reverse_stackable_values_spec.rb deleted file mode 100644 index c5ffbd293..000000000 --- a/spec/grape/util/reverse_stackable_values_spec.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -module Grape - module Util - describe ReverseStackableValues do - let(:parent) { described_class.new } - subject { described_class.new(parent) } - - describe '#keys' do - it 'returns all keys' do - subject[:some_thing] = :foo_bar - subject[:some_thing_else] = :foo_bar - expect(subject.keys).to eq %i[some_thing some_thing_else].sort - end - - it 'returns merged keys with parent' do - parent[:some_thing] = :foo - parent[:some_thing_else] = :foo - - subject[:some_thing] = :foo_bar - subject[:some_thing_more] = :foo_bar - - expect(subject.keys).to eq %i[some_thing some_thing_else some_thing_more].sort - end - end - - describe '#delete' do - it 'deletes a key' do - subject[:some_thing] = :new_foo_bar - subject.delete :some_thing - expect(subject[:some_thing]).to eq [] - end - - it 'does not delete parent values' do - parent[:some_thing] = :foo - subject[:some_thing] = :new_foo_bar - subject.delete :some_thing - expect(subject[:some_thing]).to eq [:foo] - end - end - - describe '#[]' do - it 'returns an array of values' do - subject[:some_thing] = :foo - expect(subject[:some_thing]).to eq [:foo] - end - - it 'returns parent value when no value is set' do - parent[:some_thing] = :foo - expect(subject[:some_thing]).to eq [:foo] - end - - it 'combines parent and actual values (actual first)' do - parent[:some_thing] = :foo - subject[:some_thing] = :foo_bar - expect(subject[:some_thing]).to eq %i[foo_bar foo] - end - - it 'parent values are not changed' do - parent[:some_thing] = :foo - subject[:some_thing] = :foo_bar - expect(parent[:some_thing]).to eq [:foo] - end - end - - describe '#[]=' do - it 'sets a value' do - subject[:some_thing] = :foo - expect(subject[:some_thing]).to eq [:foo] - end - - it 'pushes further values' do - subject[:some_thing] = :foo - subject[:some_thing] = :bar - expect(subject[:some_thing]).to eq %i[foo bar] - end - - it 'can handle array values' do - subject[:some_thing] = :foo - subject[:some_thing] = %i[bar more] - expect(subject[:some_thing]).to eq [:foo, %i[bar more]] - - parent[:some_thing_else] = %i[foo bar] - subject[:some_thing_else] = %i[some bar foo] - - expect(subject[:some_thing_else]).to eq [%i[some bar foo], %i[foo bar]] - end - end - - describe '#to_hash' do - it 'returns a Hash representation' do - parent[:some_thing] = :foo - subject[:some_thing] = %i[bar more] - subject[:some_thing_more] = :foo_bar - expect(subject.to_hash).to eq( - some_thing: [%i[bar more], :foo], - some_thing_more: [:foo_bar] - ) - end - end - - describe '#clone' do - let(:obj_cloned) { subject.clone } - it 'copies all values' do - parent = described_class.new - child = described_class.new parent - grandchild = described_class.new child - - parent[:some_thing] = :foo - child[:some_thing] = %i[bar more] - grandchild[:some_thing] = :grand_foo_bar - grandchild[:some_thing_more] = :foo_bar - - expect(grandchild.clone.to_hash).to eq( - some_thing: [:grand_foo_bar, %i[bar more], :foo], - some_thing_more: [:foo_bar] - ) - end - - context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do - let(:middleware) { double } - - before { subject[:middleware] = middleware } - - it 'copies values; does not duplicate them' do - expect(obj_cloned[:middleware]).to eq [middleware] - end - end - end - end - end -end diff --git a/spec/grape/util/stackable_values_spec.rb b/spec/grape/util/stackable_values_spec.rb index 07e6e4a98..c7defdf75 100644 --- a/spec/grape/util/stackable_values_spec.rb +++ b/spec/grape/util/stackable_values_spec.rb @@ -8,7 +8,7 @@ module Util subject { StackableValues.new(parent) } describe '#keys' do - it 'returns all key' do + it 'returns all keys' do subject[:some_thing] = :foo_bar subject[:some_thing_else] = :foo_bar expect(subject.keys).to eq %i[some_thing some_thing_else].sort From dd8a5f66aa4644eeccdd9f317442d2f4a745f80f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 8 Jan 2020 20:30:56 +1300 Subject: [PATCH 157/290] Enable support for `rspec --next-failure`. --- spec/spec_helper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 269958695..2c63c5726 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,6 +25,9 @@ config.filter_run_when_matching :focus config.before(:each) { Grape::Util::InheritableSetting.reset_global! } + + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" end require 'coveralls' From 3f99242c1b2252e3bbec52689b890b22e830a3d0 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 8 Jan 2020 20:31:09 +1300 Subject: [PATCH 158/290] Ensure the response is an array. --- lib/grape/middleware/base.rb | 2 +- spec/grape/api_spec.rb | 6 +++--- spec/grape/endpoint_spec.rb | 2 +- spec/grape/integration/rack_spec.rb | 2 +- spec/grape/middleware/formatter_spec.rb | 10 +++++----- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index bc96feebf..f3b18a92d 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -25,7 +25,7 @@ def default_options end def call(env) - dup.call!(env) + dup.call!(env).to_a end def call!(env) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index b506ac0ee..22d2050aa 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1903,9 +1903,9 @@ def foo it 'avoids polluting global namespace' do env = Rack::MockRequest.env_for('/') - expect(a.call(env)[2].body).to eq(['foo']) - expect(b.call(env)[2].body).to eq(['bar']) - expect(a.call(env)[2].body).to eq(['foo']) + expect(a.call(env)[2]).to eq(['foo']) + expect(b.call(env)[2]).to eq(['bar']) + expect(a.call(env)[2]).to eq(['foo']) end end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 13bc92a89..26faf2293 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -151,7 +151,7 @@ def app it 'includes headers passed as symbols' do env = Rack::MockRequest.env_for('/headers') env['HTTP_SYMBOL_HEADER'.to_sym] = 'Goliath passes symbols' - body = subject.call(env)[2].body.first + body = subject.call(env)[2].first expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols') end end diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index cc339ddec..c66e0f263 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -21,7 +21,7 @@ } env = Rack::MockRequest.env_for('/', options) - expect(JSON.parse(app.call(env)[2].body.first)['params_keys']).to match_array('test') + expect(JSON.parse(app.call(env)[2].first)['params_keys']).to match_array('test') ensure input.close input.unlink diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index e26ba43f5..41ef3d4df 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -196,19 +196,19 @@ def to_xml subject.options[:content_types][:custom] = "don't care" subject.options[:formatters][:custom] = ->(_obj, _env) { 'CUSTOM FORMAT' } _, _, body = subject.call('PATH_INFO' => '/info.custom') - expect(body.body).to eq(['CUSTOM FORMAT']) + expect(body).to eq(['CUSTOM FORMAT']) end context 'default' do let(:body) { ['blah'] } it 'uses default json formatter' do _, _, body = subject.call('PATH_INFO' => '/info.json') - expect(body.body).to eq(['["blah"]']) + expect(body).to eq(['["blah"]']) end end it 'uses custom json formatter' do subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' } _, _, body = subject.call('PATH_INFO' => '/info.json') - expect(body.body).to eq(['CUSTOM JSON FORMAT']) + expect(body).to eq(['CUSTOM JSON FORMAT']) end end @@ -384,7 +384,7 @@ def to_xml it 'returns Grape::Uril::SendFileReponse' do env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' } - expect(subject.call(env)).to be_a(Grape::ServeFile::SendfileResponse) + expect(subject.call(env)).to be_a(Array) end end @@ -407,7 +407,7 @@ def self.call(_, _) it 'returns response by invalid formatter' do env = { 'PATH_INFO' => '/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid' } _, _, bodies = *subject.call(env) - expect(bodies.body.first).to eq({ message: 'invalid' }.to_json) + expect(bodies.first).to eq({ message: 'invalid' }.to_json) end end From 6a447bb94d22dac2edb10b3265af84912cf2e407 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 8 Jan 2020 22:15:32 +1300 Subject: [PATCH 159/290] Fix specs and test on rack ~> 1.0 and rack ~> 2.0.0. --- .travis.yml | 4 ++++ gemfiles/rack1.gemfile | 4 ++++ gemfiles/rack2-0.gemfile | 4 ++++ spec/grape/middleware/formatter_spec.rb | 12 ++++++++---- 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 gemfiles/rack1.gemfile create mode 100644 gemfiles/rack2-0.gemfile diff --git a/.travis.yml b/.travis.yml index ac4a84eb5..491fe4464 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ matrix: - bundle exec danger - rvm: 2.7.0 gemfile: Gemfile + - rvm: 2.7.0 + gemfile: gemfiles/rack1.gemfile + - rvm: 2.7.0 + gemfile: gemfiles/rack2-0.gemfile - rvm: 2.7.0 gemfile: gemfiles/rack_edge.gemfile - rvm: 2.7.0 diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile new file mode 100644 index 000000000..c25fcb2ef --- /dev/null +++ b/gemfiles/rack1.gemfile @@ -0,0 +1,4 @@ + +eval_gemfile("../Gemfile") + +gem 'rack', '~> 1.0' diff --git a/gemfiles/rack2-0.gemfile b/gemfiles/rack2-0.gemfile new file mode 100644 index 000000000..c25fcb2ef --- /dev/null +++ b/gemfiles/rack2-0.gemfile @@ -0,0 +1,4 @@ + +eval_gemfile("../Gemfile") + +gem 'rack', '~> 1.0' diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index 41ef3d4df..fb0a17b09 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -379,12 +379,16 @@ def to_xml end context 'send file' do - let(:body) { Grape::ServeFile::FileResponse.new('file') } - let(:app) { ->(_env) { [200, {}, body] } } + let(:file) {double(File)} + let(:file_body) { Grape::ServeFile::FileResponse.new(file) } + let(:app) { ->(_env) { [200, {}, file_body] } } - it 'returns Grape::Uril::SendFileReponse' do + it 'returns a file response' do env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' } - expect(subject.call(env)).to be_a(Array) + status, headers, body = subject.call(env) + expect(status).to be == 200 + expect(headers).to be == {"Content-Type"=>"application/json"} + expect(body).to be file end end From 9cbb48c42afc44727b71ae3efd3251fdd01f7ac8 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 8 Jan 2020 22:29:03 +1300 Subject: [PATCH 160/290] Oh RuboCop... --- gemfiles/rack1.gemfile | 3 ++- gemfiles/rack2-0.gemfile | 3 ++- spec/grape/middleware/formatter_spec.rb | 4 ++-- spec/spec_helper.rb | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile index c25fcb2ef..362af946f 100644 --- a/gemfiles/rack1.gemfile +++ b/gemfiles/rack1.gemfile @@ -1,4 +1,5 @@ +# frozen_string_literal: true -eval_gemfile("../Gemfile") +eval_gemfile('../Gemfile') gem 'rack', '~> 1.0' diff --git a/gemfiles/rack2-0.gemfile b/gemfiles/rack2-0.gemfile index c25fcb2ef..362af946f 100644 --- a/gemfiles/rack2-0.gemfile +++ b/gemfiles/rack2-0.gemfile @@ -1,4 +1,5 @@ +# frozen_string_literal: true -eval_gemfile("../Gemfile") +eval_gemfile('../Gemfile') gem 'rack', '~> 1.0' diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index fb0a17b09..ae4519c92 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -379,7 +379,7 @@ def to_xml end context 'send file' do - let(:file) {double(File)} + let(:file) { double(File) } let(:file_body) { Grape::ServeFile::FileResponse.new(file) } let(:app) { ->(_env) { [200, {}, file_body] } } @@ -387,7 +387,7 @@ def to_xml env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' } status, headers, body = subject.call(env) expect(status).to be == 200 - expect(headers).to be == {"Content-Type"=>"application/json"} + expect(headers).to be == { 'Content-Type' => 'application/json' } expect(body).to be file end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2c63c5726..7745de7dc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -27,7 +27,7 @@ config.before(:each) { Grape::Util::InheritableSetting.reset_global! } # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = ".rspec_status" + config.example_status_persistence_file_path = '.rspec_status' end require 'coveralls' From 5dd9913412bc19607c4dcfebc16d7f62b621c353 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 8 Jan 2020 23:16:01 +1300 Subject: [PATCH 161/290] Correctly read chunks of the response body according to the Rack SPEC. --- spec/grape/api_spec.rb | 6 +++--- spec/grape/endpoint_spec.rb | 2 +- spec/grape/integration/rack_spec.rb | 2 +- spec/grape/middleware/formatter_spec.rb | 13 +++++++------ spec/spec_helper.rb | 10 ++++++++++ 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 22d2050aa..a199e33d0 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1903,9 +1903,9 @@ def foo it 'avoids polluting global namespace' do env = Rack::MockRequest.env_for('/') - expect(a.call(env)[2]).to eq(['foo']) - expect(b.call(env)[2]).to eq(['bar']) - expect(a.call(env)[2]).to eq(['foo']) + expect(read_chunks(a.call(env)[2])).to eq(['foo']) + expect(read_chunks(b.call(env)[2])).to eq(['bar']) + expect(read_chunks(a.call(env)[2])).to eq(['foo']) end end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 26faf2293..37aea84bc 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -151,7 +151,7 @@ def app it 'includes headers passed as symbols' do env = Rack::MockRequest.env_for('/headers') env['HTTP_SYMBOL_HEADER'.to_sym] = 'Goliath passes symbols' - body = subject.call(env)[2].first + body = read_chunks(subject.call(env)[2]).join expect(JSON.parse(body)['Symbol-Header']).to eq('Goliath passes symbols') end end diff --git a/spec/grape/integration/rack_spec.rb b/spec/grape/integration/rack_spec.rb index c66e0f263..e1c46762f 100644 --- a/spec/grape/integration/rack_spec.rb +++ b/spec/grape/integration/rack_spec.rb @@ -21,7 +21,7 @@ } env = Rack::MockRequest.env_for('/', options) - expect(JSON.parse(app.call(env)[2].first)['params_keys']).to match_array('test') + expect(JSON.parse(read_chunks(app.call(env)[2]).join)['params_keys']).to match_array('test') ensure input.close input.unlink diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index ae4519c92..102bca8f1 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -196,19 +196,19 @@ def to_xml subject.options[:content_types][:custom] = "don't care" subject.options[:formatters][:custom] = ->(_obj, _env) { 'CUSTOM FORMAT' } _, _, body = subject.call('PATH_INFO' => '/info.custom') - expect(body).to eq(['CUSTOM FORMAT']) + expect(read_chunks(body)).to eq(['CUSTOM FORMAT']) end context 'default' do let(:body) { ['blah'] } it 'uses default json formatter' do _, _, body = subject.call('PATH_INFO' => '/info.json') - expect(body).to eq(['["blah"]']) + expect(read_chunks(body)).to eq(['["blah"]']) end end it 'uses custom json formatter' do subject.options[:formatters][:json] = ->(_obj, _env) { 'CUSTOM JSON FORMAT' } _, _, body = subject.call('PATH_INFO' => '/info.json') - expect(body).to eq(['CUSTOM JSON FORMAT']) + expect(read_chunks(body)).to eq(['CUSTOM JSON FORMAT']) end end @@ -384,11 +384,12 @@ def to_xml let(:app) { ->(_env) { [200, {}, file_body] } } it 'returns a file response' do + expect(file).to receive(:each).and_yield('data') env = { 'PATH_INFO' => '/somewhere', 'HTTP_ACCEPT' => 'application/json' } status, headers, body = subject.call(env) expect(status).to be == 200 expect(headers).to be == { 'Content-Type' => 'application/json' } - expect(body).to be file + expect(read_chunks(body)).to be == ['data'] end end @@ -410,8 +411,8 @@ def self.call(_, _) it 'returns response by invalid formatter' do env = { 'PATH_INFO' => '/hello.invalid', 'HTTP_ACCEPT' => 'application/x-invalid' } - _, _, bodies = *subject.call(env) - expect(bodies.first).to eq({ message: 'invalid' }.to_json) + _, _, body = *subject.call(env) + expect(read_chunks(body).join).to eq({ message: 'invalid' }.to_json) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7745de7dc..fa4f8bf00 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,7 +18,17 @@ # so it should be set to true here as well to reflect that. I18n.enforce_available_locales = true +module Chunks + def read_chunks(body) + buffer = [] + body.each { |chunk| buffer << chunk } + + buffer + end +end + RSpec.configure do |config| + config.include Chunks config.include Rack::Test::Methods config.include Spec::Support::Helpers config.raise_errors_for_deprecations! From 2c27ef9673368297f8934871274d9e2f2e137bf5 Mon Sep 17 00:00:00 2001 From: dm1try Date: Thu, 9 Jan 2020 23:12:40 +0300 Subject: [PATCH 162/290] ignore .rspec_status --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e139fdf5b..9e152a5d4 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ pkg .rvmrc .ruby-version .ruby-gemset +.rspec_status .bundle .byebug_history dist From 38c6bc0d224a190e23f2c87c57cd943594f9b344 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.) @dblockdotorg" Date: Thu, 9 Jan 2020 15:27:13 -0500 Subject: [PATCH 163/290] Revert "delete a reversible stackable values class (#1953)" This reverts commit 9f786adbffa25574916e5d5504dacb4f5e79c6bb. --- CHANGELOG.md | 1 - lib/grape.rb | 1 + lib/grape/dsl/request_response.rb | 2 +- lib/grape/dsl/settings.rb | 15 +- lib/grape/endpoint.rb | 20 ++- lib/grape/util/inheritable_setting.rb | 8 +- lib/grape/util/reverse_stackable_values.rb | 18 +++ lib/grape/util/stackable_values.rb | 1 - lib/grape/validations/attributes_iterator.rb | 3 +- lib/grape/validations/params_scope.rb | 10 +- lib/grape/validations/types/build_coercer.rb | 6 +- spec/grape/api/instance_spec.rb | 6 - spec/grape/dsl/request_response_spec.rb | 10 +- spec/grape/util/inheritable_setting_spec.rb | 23 +++ .../util/reverse_stackable_values_spec.rb | 133 ++++++++++++++++++ spec/grape/util/stackable_values_spec.rb | 2 +- 16 files changed, 219 insertions(+), 40 deletions(-) create mode 100644 lib/grape/util/reverse_stackable_values.rb create mode 100644 spec/grape/util/reverse_stackable_values_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 47151226b..e3527613c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ #### Features * Your contribution here. -* [#1953](https://github.com/ruby-grape/grape/pull/1953): Delete a reversible stackable values class - [@dnesteryuk](https://github.com/dnesteryuk). * [#1949](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj). * [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj). * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape.rb b/lib/grape.rb index 8d3459481..388fb54bd 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -140,6 +140,7 @@ module Util eager_autoload do autoload :InheritableValues autoload :StackableValues + autoload :ReverseStackableValues autoload :InheritableSetting autoload :StrictHashConfiguration autoload :Registrable diff --git a/lib/grape/dsl/request_response.rb b/lib/grape/dsl/request_response.rb index cab80eea9..cbb1db912 100644 --- a/lib/grape/dsl/request_response.rb +++ b/lib/grape/dsl/request_response.rb @@ -127,7 +127,7 @@ def rescue_from(*args, &block) :base_only_rescue_handlers end - namespace_stackable handler_type, Hash[args.map { |arg| [arg, handler] }] + namespace_reverse_stackable handler_type, Hash[args.map { |arg| [arg, handler] }] end namespace_stackable(:rescue_options, options) diff --git a/lib/grape/dsl/settings.rb b/lib/grape/dsl/settings.rb index 086b238d7..eebd12750 100644 --- a/lib/grape/dsl/settings.rb +++ b/lib/grape/dsl/settings.rb @@ -96,6 +96,10 @@ def namespace_stackable(key, value = nil) get_or_set :namespace_stackable, key, value end + def namespace_reverse_stackable(key, value = nil) + get_or_set :namespace_reverse_stackable, key, value + end + def namespace_stackable_with_hash(key) settings = get_or_set :namespace_stackable, key, nil return if settings.blank? @@ -103,11 +107,10 @@ def namespace_stackable_with_hash(key) end def namespace_reverse_stackable_with_hash(key) - settings = get_or_set :namespace_stackable, key, nil + settings = get_or_set :namespace_reverse_stackable, key, nil return if settings.blank? - result = {} - settings.reverse_each do |setting| + settings.each do |setting| setting.each do |field, value| result[field] ||= value end @@ -168,11 +171,7 @@ def within_namespace(&_block) # the superclass's :inheritable_setting. def build_top_level_setting Grape::Util::InheritableSetting.new.tap do |setting| - # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to - # +inheritable_setting+, however, it doesn't contain any user-defined settings. - # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+ - # in the chain for every endpoint. - if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance + if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API setting.inherit_from superclass.inheritable_setting end end diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 6b51a622f..3fb417874 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -386,8 +386,24 @@ def run_filters(filters, type = :other) extend post_extension if post_extension end - %i[befores before_validations after_validations afters finallies].each do |meth_id| - define_method(meth_id) { namespace_stackable(meth_id) } + def befores + namespace_stackable(:befores) || [] + end + + def before_validations + namespace_stackable(:before_validations) || [] + end + + def after_validations + namespace_stackable(:after_validations) || [] + end + + def afters + namespace_stackable(:afters) || [] + end + + def finallies + namespace_stackable(:finallies) || [] end def validations diff --git a/lib/grape/util/inheritable_setting.rb b/lib/grape/util/inheritable_setting.rb index befc6bd68..2a6024bca 100644 --- a/lib/grape/util/inheritable_setting.rb +++ b/lib/grape/util/inheritable_setting.rb @@ -6,7 +6,7 @@ module Util # and inheritable values (see InheritableValues and StackableValues). class InheritableSetting attr_accessor :route, :api_class, :namespace - attr_accessor :namespace_inheritable, :namespace_stackable + attr_accessor :namespace_inheritable, :namespace_stackable, :namespace_reverse_stackable attr_accessor :parent, :point_in_time_copies # Retrieve global settings. @@ -31,6 +31,7 @@ def initialize # used with a mount, or should every API::Class be a separate namespace by default? self.namespace_inheritable = InheritableValues.new self.namespace_stackable = StackableValues.new + self.namespace_reverse_stackable = ReverseStackableValues.new self.point_in_time_copies = [] @@ -53,6 +54,7 @@ def inherit_from(parent) namespace_inheritable.inherited_values = parent.namespace_inheritable namespace_stackable.inherited_values = parent.namespace_stackable + namespace_reverse_stackable.inherited_values = parent.namespace_reverse_stackable self.route = parent.route.merge(route) point_in_time_copies.map { |cloned_one| cloned_one.inherit_from parent } @@ -70,6 +72,7 @@ def point_in_time_copy new_setting.namespace = namespace.clone new_setting.namespace_inheritable = namespace_inheritable.clone new_setting.namespace_stackable = namespace_stackable.clone + new_setting.namespace_reverse_stackable = namespace_reverse_stackable.clone new_setting.route = route.clone new_setting.api_class = api_class @@ -90,7 +93,8 @@ def to_hash route: route.clone, namespace: namespace.to_hash, namespace_inheritable: namespace_inheritable.to_hash, - namespace_stackable: namespace_stackable.to_hash + namespace_stackable: namespace_stackable.to_hash, + namespace_reverse_stackable: namespace_reverse_stackable.to_hash } end end diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb new file mode 100644 index 000000000..d8e8f160c --- /dev/null +++ b/lib/grape/util/reverse_stackable_values.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative 'stackable_values' + +module Grape + module Util + class ReverseStackableValues < StackableValues + protected + + def concat_values(inherited_value, new_value) + [].tap do |value| + value.concat(new_value) + value.concat(inherited_value) + end + end + end + end +end diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 79d3f3777..324aa19db 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -13,7 +13,6 @@ def initialize(*_args) @frozen_values = {} end - # Even if there is no value, an empty array will be returned. def [](name) return @frozen_values[name] if @frozen_values.key? name diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index bfa69cfc6..6c53d469a 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -11,7 +11,6 @@ def initialize(validator, scope, params) @scope = scope @attrs = validator.attrs @original_params = scope.params(params) - @array_given = @original_params.is_a?(Array) @params = Array.wrap(@original_params) end @@ -31,7 +30,7 @@ def do_each(params_to_process, parent_indicies = [], &block) end if @scope.type == Array - next unless @array_given # do not validate content of array if it isn't array + next unless @original_params.is_a?(Array) # do not validate content of array if it isn't array # fill current and parent scopes with correct array indicies parent_scope = @scope.parent diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index c8b1527c0..0cf8d2ef7 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -45,10 +45,8 @@ def configuration # @return [Boolean] whether or not this entire scope needs to be # validated def should_validate?(parameters) - scoped_params = params(parameters) - - return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params)) - return false unless meets_dependency?(scoped_params, parameters) + return false if @optional && (params(parameters).blank? || all_element_blank?(parameters)) + return false unless meets_dependency?(params(parameters), parameters) return true if parent.nil? parent.should_validate?(parameters) end @@ -448,8 +446,8 @@ def options_key?(type, key, validations) validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil? end - def all_element_blank?(scoped_params) - scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?) + def all_element_blank?(parameters) + params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?) end # Validators don't have access to each other and they don't need, however, diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index fc07ad4b4..fc0762121 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -83,11 +83,7 @@ def self.cache_instance(type, method, strict, &_block) end def self.cache_key(type, method, strict) - [type, method, strict].each_with_object(+'') do |val, memo| - next if val.nil? - - memo << '_' << val.to_s - end + [type, method, strict].compact.map(&:to_s).join('_') end instance_variable_set(:@__cache, {}) diff --git a/spec/grape/api/instance_spec.rb b/spec/grape/api/instance_spec.rb index fa5c949e5..6de77c51e 100644 --- a/spec/grape/api/instance_spec.rb +++ b/spec/grape/api/instance_spec.rb @@ -45,10 +45,4 @@ def app expect(last_response.body).to eq 'success' end end - - context 'top level setting' do - it 'does not inherit settings from the superclass (Grape::API::Instance)' do - expect(an_instance.top_level_setting.parent).to be_nil - end - end end diff --git a/spec/grape/dsl/request_response_spec.rb b/spec/grape/dsl/request_response_spec.rb index 515e48cb0..00d9e3a6a 100644 --- a/spec/grape/dsl/request_response_spec.rb +++ b/spec/grape/dsl/request_response_spec.rb @@ -161,34 +161,34 @@ def self.imbue(key, value) describe 'list of exceptions is passed' do it 'sets hash of exceptions as rescue handlers' do - expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => nil) + expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => nil) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError end it 'rescues only base handlers if rescue_subclasses: false option is passed' do - expect(subject).to receive(:namespace_stackable).with(:base_only_rescue_handlers, StandardError => nil) + expect(subject).to receive(:namespace_reverse_stackable).with(:base_only_rescue_handlers, StandardError => nil) expect(subject).to receive(:namespace_stackable).with(:rescue_options, rescue_subclasses: false) subject.rescue_from StandardError, rescue_subclasses: false end it 'sets given proc as rescue handler for each key in hash' do rescue_handler_proc = proc {} - expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) + expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError, rescue_handler_proc end it 'sets given block as rescue handler for each key in hash' do rescue_handler_proc = proc {} - expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) + expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => rescue_handler_proc) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError, &rescue_handler_proc end it 'sets a rescue handler declared through :with option for each key in hash' do with_block = -> { 'hello' } - expect(subject).to receive(:namespace_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc)) + expect(subject).to receive(:namespace_reverse_stackable).with(:rescue_handlers, StandardError => an_instance_of(Proc)) expect(subject).to receive(:namespace_stackable).with(:rescue_options, {}) subject.rescue_from StandardError, with: with_block end diff --git a/spec/grape/util/inheritable_setting_spec.rb b/spec/grape/util/inheritable_setting_spec.rb index 6b3b44ff0..0e0b672e7 100644 --- a/spec/grape/util/inheritable_setting_spec.rb +++ b/spec/grape/util/inheritable_setting_spec.rb @@ -14,6 +14,7 @@ module Util settings.namespace[:namespace_thing] = :namespace_foo_bar settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar + settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar settings.route[:route_thing] = :route_foo_bar end end @@ -23,6 +24,7 @@ module Util settings.namespace[:namespace_thing] = :namespace_foo_bar_other settings.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar_other settings.namespace_stackable[:namespace_stackable_thing] = :namespace_stackable_foo_bar_other + settings.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :namespace_reverse_stackable_foo_bar_other settings.route[:route_thing] = :route_foo_bar_other end end @@ -120,6 +122,16 @@ module Util end end + describe '#namespace_reverse_stackable' do + it 'works with reverse stackable values' do + expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar] + + subject.inherit_from other_parent + + expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar_other] + end + end + describe '#route' do it 'sets a value until the next route' do subject.route[:some_thing] = :foo_bar @@ -186,6 +198,14 @@ module Util expect(cloned_obj.namespace_stackable[:namespace_stackable_thing]).to eq [:namespace_stackable_foo_bar] end + it 'decouples namespace reverse stackable values' do + expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar] + + subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = :other_thing + expect(subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq %i[other_thing namespace_reverse_stackable_foo_bar] + expect(cloned_obj.namespace_reverse_stackable[:namespace_reverse_stackable_thing]).to eq [:namespace_reverse_stackable_foo_bar] + end + it 'decouples route values' do expect(cloned_obj.route[:route_thing]).to eq :route_foo_bar @@ -204,6 +224,7 @@ module Util subject.namespace[:namespace_thing] = :namespace_foo_bar subject.namespace_inheritable[:namespace_inheritable_thing] = :namespace_inheritable_foo_bar subject.namespace_stackable[:namespace_stackable_thing] = [:namespace_stackable_foo_bar] + subject.namespace_reverse_stackable[:namespace_reverse_stackable_thing] = [:namespace_reverse_stackable_foo_bar] subject.route[:route_thing] = :route_foo_bar expect(subject.to_hash).to include(global: { global_thing: :global_foo_bar }) @@ -212,6 +233,8 @@ module Util namespace_inheritable_thing: :namespace_inheritable_foo_bar }) expect(subject.to_hash).to include(namespace_stackable: { namespace_stackable_thing: [:namespace_stackable_foo_bar, [:namespace_stackable_foo_bar]] }) + expect(subject.to_hash).to include(namespace_reverse_stackable: + { namespace_reverse_stackable_thing: [[:namespace_reverse_stackable_foo_bar], :namespace_reverse_stackable_foo_bar] }) expect(subject.to_hash).to include(route: { route_thing: :route_foo_bar }) end end diff --git a/spec/grape/util/reverse_stackable_values_spec.rb b/spec/grape/util/reverse_stackable_values_spec.rb new file mode 100644 index 000000000..c5ffbd293 --- /dev/null +++ b/spec/grape/util/reverse_stackable_values_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'spec_helper' +module Grape + module Util + describe ReverseStackableValues do + let(:parent) { described_class.new } + subject { described_class.new(parent) } + + describe '#keys' do + it 'returns all keys' do + subject[:some_thing] = :foo_bar + subject[:some_thing_else] = :foo_bar + expect(subject.keys).to eq %i[some_thing some_thing_else].sort + end + + it 'returns merged keys with parent' do + parent[:some_thing] = :foo + parent[:some_thing_else] = :foo + + subject[:some_thing] = :foo_bar + subject[:some_thing_more] = :foo_bar + + expect(subject.keys).to eq %i[some_thing some_thing_else some_thing_more].sort + end + end + + describe '#delete' do + it 'deletes a key' do + subject[:some_thing] = :new_foo_bar + subject.delete :some_thing + expect(subject[:some_thing]).to eq [] + end + + it 'does not delete parent values' do + parent[:some_thing] = :foo + subject[:some_thing] = :new_foo_bar + subject.delete :some_thing + expect(subject[:some_thing]).to eq [:foo] + end + end + + describe '#[]' do + it 'returns an array of values' do + subject[:some_thing] = :foo + expect(subject[:some_thing]).to eq [:foo] + end + + it 'returns parent value when no value is set' do + parent[:some_thing] = :foo + expect(subject[:some_thing]).to eq [:foo] + end + + it 'combines parent and actual values (actual first)' do + parent[:some_thing] = :foo + subject[:some_thing] = :foo_bar + expect(subject[:some_thing]).to eq %i[foo_bar foo] + end + + it 'parent values are not changed' do + parent[:some_thing] = :foo + subject[:some_thing] = :foo_bar + expect(parent[:some_thing]).to eq [:foo] + end + end + + describe '#[]=' do + it 'sets a value' do + subject[:some_thing] = :foo + expect(subject[:some_thing]).to eq [:foo] + end + + it 'pushes further values' do + subject[:some_thing] = :foo + subject[:some_thing] = :bar + expect(subject[:some_thing]).to eq %i[foo bar] + end + + it 'can handle array values' do + subject[:some_thing] = :foo + subject[:some_thing] = %i[bar more] + expect(subject[:some_thing]).to eq [:foo, %i[bar more]] + + parent[:some_thing_else] = %i[foo bar] + subject[:some_thing_else] = %i[some bar foo] + + expect(subject[:some_thing_else]).to eq [%i[some bar foo], %i[foo bar]] + end + end + + describe '#to_hash' do + it 'returns a Hash representation' do + parent[:some_thing] = :foo + subject[:some_thing] = %i[bar more] + subject[:some_thing_more] = :foo_bar + expect(subject.to_hash).to eq( + some_thing: [%i[bar more], :foo], + some_thing_more: [:foo_bar] + ) + end + end + + describe '#clone' do + let(:obj_cloned) { subject.clone } + it 'copies all values' do + parent = described_class.new + child = described_class.new parent + grandchild = described_class.new child + + parent[:some_thing] = :foo + child[:some_thing] = %i[bar more] + grandchild[:some_thing] = :grand_foo_bar + grandchild[:some_thing_more] = :foo_bar + + expect(grandchild.clone.to_hash).to eq( + some_thing: [:grand_foo_bar, %i[bar more], :foo], + some_thing_more: [:foo_bar] + ) + end + + context 'complex (i.e. not primitive) data types (ex. middleware, please see bug #930)' do + let(:middleware) { double } + + before { subject[:middleware] = middleware } + + it 'copies values; does not duplicate them' do + expect(obj_cloned[:middleware]).to eq [middleware] + end + end + end + end + end +end diff --git a/spec/grape/util/stackable_values_spec.rb b/spec/grape/util/stackable_values_spec.rb index c7defdf75..07e6e4a98 100644 --- a/spec/grape/util/stackable_values_spec.rb +++ b/spec/grape/util/stackable_values_spec.rb @@ -8,7 +8,7 @@ module Util subject { StackableValues.new(parent) } describe '#keys' do - it 'returns all keys' do + it 'returns all key' do subject[:some_thing] = :foo_bar subject[:some_thing_else] = :foo_bar expect(subject.keys).to eq %i[some_thing some_thing_else].sort From 951d6d076ae66617a0952a34f7a468a8c992b9ad Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 9 Jan 2020 15:33:36 -0500 Subject: [PATCH 164/290] Fix: typo, key -> keys. --- spec/grape/util/stackable_values_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/grape/util/stackable_values_spec.rb b/spec/grape/util/stackable_values_spec.rb index 07e6e4a98..c7defdf75 100644 --- a/spec/grape/util/stackable_values_spec.rb +++ b/spec/grape/util/stackable_values_spec.rb @@ -8,7 +8,7 @@ module Util subject { StackableValues.new(parent) } describe '#keys' do - it 'returns all key' do + it 'returns all keys' do subject[:some_thing] = :foo_bar subject[:some_thing_else] = :foo_bar expect(subject.keys).to eq %i[some_thing some_thing_else].sort From 54e823b3fbf2c62e0688aa9b5ad8375e31917320 Mon Sep 17 00:00:00 2001 From: dm1try Date: Thu, 9 Jan 2020 04:00:35 +0300 Subject: [PATCH 165/290] add failing spec for .rescue_from regression see #1957 --- spec/grape/api_spec.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index a199e33d0..108043d50 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1942,6 +1942,26 @@ def foo expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake') end + it 'mimics default ruby "rescue" handler' do + # The exception is matched to the rescue starting at the top, and matches only once + + subject.rescue_from ArgumentError do |e| + error!(e, 402) + end + subject.rescue_from StandardError do |e| + error!(e, 401) + end + + subject.get('/child_of_standard_error') { raise ArgumentError } + subject.get('/standard_error') { raise StandardError } + + get '/child_of_standard_error' + expect(last_response.status).to eql 402 + + get '/standard_error' + expect(last_response.status).to eql 401 + end + context 'CustomError subclass of Grape::Exceptions::Base' do before do module ApiSpec From fe50fe8804186be0f59fe05614522e3debd5eb26 Mon Sep 17 00:00:00 2001 From: dblock Date: Thu, 9 Jan 2020 16:18:56 -0500 Subject: [PATCH 166/290] Re-apply the micro-optimizations and parent fix from #1961. --- lib/grape/dsl/settings.rb | 6 +++++- lib/grape/util/stackable_values.rb | 1 + lib/grape/validations/params_scope.rb | 10 ++++++---- lib/grape/validations/types/build_coercer.rb | 6 +++++- spec/grape/api/instance_spec.rb | 6 ++++++ 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/grape/dsl/settings.rb b/lib/grape/dsl/settings.rb index eebd12750..706f8adb6 100644 --- a/lib/grape/dsl/settings.rb +++ b/lib/grape/dsl/settings.rb @@ -171,7 +171,11 @@ def within_namespace(&_block) # the superclass's :inheritable_setting. def build_top_level_setting Grape::Util::InheritableSetting.new.tap do |setting| - if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API + # Doesn't try to inherit settings from +Grape::API::Instance+ which also responds to + # +inheritable_setting+, however, it doesn't contain any user-defined settings. + # Otherwise, it would lead to an extra instance of +Grape::Util::InheritableSetting+ + # in the chain for every endpoint. + if defined?(superclass) && superclass.respond_to?(:inheritable_setting) && superclass != Grape::API::Instance setting.inherit_from superclass.inheritable_setting end end diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 324aa19db..79d3f3777 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -13,6 +13,7 @@ def initialize(*_args) @frozen_values = {} end + # Even if there is no value, an empty array will be returned. def [](name) return @frozen_values[name] if @frozen_values.key? name diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 0cf8d2ef7..c8b1527c0 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -45,8 +45,10 @@ def configuration # @return [Boolean] whether or not this entire scope needs to be # validated def should_validate?(parameters) - return false if @optional && (params(parameters).blank? || all_element_blank?(parameters)) - return false unless meets_dependency?(params(parameters), parameters) + scoped_params = params(parameters) + + return false if @optional && (scoped_params.blank? || all_element_blank?(scoped_params)) + return false unless meets_dependency?(scoped_params, parameters) return true if parent.nil? parent.should_validate?(parameters) end @@ -446,8 +448,8 @@ def options_key?(type, key, validations) validations[type].respond_to?(:key?) && validations[type].key?(key) && !validations[type][key].nil? end - def all_element_blank?(parameters) - params(parameters).respond_to?(:all?) && params(parameters).all?(&:blank?) + def all_element_blank?(scoped_params) + scoped_params.respond_to?(:all?) && scoped_params.all?(&:blank?) end # Validators don't have access to each other and they don't need, however, diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index fc0762121..90b1dbf01 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -83,7 +83,11 @@ def self.cache_instance(type, method, strict, &_block) end def self.cache_key(type, method, strict) - [type, method, strict].compact.map(&:to_s).join('_') + [type, method, strict].each_with_object(+'_') do |val, memo| + next if val.nil? + + memo << '_' << val.to_s + end end instance_variable_set(:@__cache, {}) diff --git a/spec/grape/api/instance_spec.rb b/spec/grape/api/instance_spec.rb index 6de77c51e..fa5c949e5 100644 --- a/spec/grape/api/instance_spec.rb +++ b/spec/grape/api/instance_spec.rb @@ -45,4 +45,10 @@ def app expect(last_response.body).to eq 'success' end end + + context 'top level setting' do + it 'does not inherit settings from the superclass (Grape::API::Instance)' do + expect(an_instance.top_level_setting.parent).to be_nil + end + end end From bcf9e81e89024b2af6f33a1c1d2a83a8fbc2dec3 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 7 Jan 2020 16:09:21 +0200 Subject: [PATCH 167/290] The values validator must properly work with booleans When we check whether a value is given or not, it is safer to look for `nil`, there are cases when `false` is ok value. --- CHANGELOG.md | 1 + lib/grape/validations/validators/values.rb | 12 ++++++++---- .../validations/validators/values_spec.rb | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3527613c..bdf574f80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ #### Fixes +* [#1963](https://github.com/ruby-grape/grape/pull/1963): The values validator must properly work with booleans - [@dnesteryuk](https://github.com/dnesteryuk). * [#1950](https://github.com/ruby-grape/grape/pull/1950): Consider the allow_blank option in the values validator - [@dnesteryuk](https://github.com/dnesteryuk). * [#1947](https://github.com/ruby-grape/grape/pull/1947): Careful check for empty params - [@dnesteryuk](https://github.com/dnesteryuk). * [#1931](https://github.com/ruby-grape/grape/pull/1946): Fixes issue when using namespaces in `Grape::API::Instance` mounted directly - [@myxoh](https://github.com/myxoh). diff --git a/lib/grape/validations/validators/values.rb b/lib/grape/validations/validators/values.rb index 0f01af1e1..7889f3bad 100644 --- a/lib/grape/validations/validators/values.rb +++ b/lib/grape/validations/validators/values.rb @@ -29,20 +29,20 @@ def validate_param!(attr_name, params) val = params[attr_name] - return unless val || required_for_root_scope? + return if val.nil? && !required_for_root_scope? # don't forget that +false.blank?+ is true return if val != false && val.blank? && @allow_blank param_array = val.nil? ? [nil] : Array.wrap(val) - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: except_message) \ + raise validation_exception(attr_name, except_message) \ unless check_excepts(param_array) - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:values)) \ + raise validation_exception(attr_name, message(:values)) \ unless check_values(param_array, attr_name) - raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:values)) \ + raise validation_exception(attr_name, message(:values)) \ if @proc && !param_array.all? { |param| @proc.call(param) } end @@ -74,6 +74,10 @@ def except_message def required_for_root_scope? @required && @scope.root? end + + def validation_exception(attr_name, message) + Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message) + end end end end diff --git a/spec/grape/validations/validators/values_spec.rb b/spec/grape/validations/validators/values_spec.rb index 81cfe6000..b975c3feb 100644 --- a/spec/grape/validations/validators/values_spec.rb +++ b/spec/grape/validations/validators/values_spec.rb @@ -438,11 +438,21 @@ def app end.to raise_error Grape::Exceptions::IncompatibleOptionValues end - it 'allows values to be true or false when setting the type to boolean' do - get('/values/optional_boolean', type: true) - expect(last_response.status).to eq 200 - expect(last_response.body).to eq({ type: true }.to_json) + context 'boolean values' do + it 'allows a value from the list' do + get('/values/optional_boolean', type: true) + + expect(last_response.status).to eq 200 + expect(last_response.body).to eq({ type: true }.to_json) + end + + it 'rejects a value which is not in the list' do + get('/values/optional_boolean', type: false) + + expect(last_response.body).to eq({ error: 'type does not have a valid value' }.to_json) + end end + it 'allows values to be a kind of the coerced type not just an instance of it' do get('/values/coercion', type: 10) expect(last_response.status).to eq 200 From 0385a45e4b249c41f2b9fa97c990de420b802ec2 Mon Sep 17 00:00:00 2001 From: "David A. Lee" Date: Fri, 10 Jan 2020 10:44:49 -0500 Subject: [PATCH 168/290] Fixes typos in README (#1965) --- CHANGELOG.md | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdf574f80..b0d5311fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ #### Fixes +* [#1965](https://github.com/ruby-grape/grape/pull/1965): Fix typos in README - [@davidalee](https://github.com/davidalee). * [#1963](https://github.com/ruby-grape/grape/pull/1963): The values validator must properly work with booleans - [@dnesteryuk](https://github.com/dnesteryuk). * [#1950](https://github.com/ruby-grape/grape/pull/1950): Consider the allow_blank option in the values validator - [@dnesteryuk](https://github.com/dnesteryuk). * [#1947](https://github.com/ruby-grape/grape/pull/1947): Careful check for empty params - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/README.md b/README.md index 231ecd3d0..0098f8c62 100644 --- a/README.md +++ b/README.md @@ -3229,7 +3229,7 @@ end Use [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper), [warden-oauth2](https://github.com/opperator/warden-oauth2) or [rack-oauth2](https://github.com/nov/rack-oauth2) for OAuth2 support. -You can access the controller params, headers, and helpers through the context with the `#context` method inside any auth middleware inherited from `Grape::Middlware::Auth::Base`. +You can access the controller params, headers, and helpers through the context with the `#context` method inside any auth middleware inherited from `Grape::Middleware::Auth::Base`. ## Describing and Inspecting an API @@ -3543,7 +3543,7 @@ class API < Grape::API end ``` -You can access the controller params, headers, and helpers through the context with the `#context` method inside any middleware inherited from `Grape::Middlware::Base`. +You can access the controller params, headers, and helpers through the context with the `#context` method inside any middleware inherited from `Grape::Middleware::Base`. ### Rails Middleware From 9ce5fb35276bea448444e2c57553cf51ae0f54a3 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 11 Jan 2020 10:22:33 +0200 Subject: [PATCH 169/290] Preparing for release, 1.3.0. --- CHANGELOG.md | 3 +-- README.md | 3 +-- lib/grape/version.rb | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d5311fd..8494be0e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,7 @@ -### 1.3.0 (Next) +### 1.3.0 (2020/01/11) #### Features -* Your contribution here. * [#1949](https://github.com/ruby-grape/grape/pull/1949): Add support for Ruby 2.7 - [@nbulaj](https://github.com/nbulaj). * [#1948](https://github.com/ruby-grape/grape/pull/1948): Relax `dry-types` dependency version - [@nbulaj](https://github.com/nbulaj). * [#1944](https://github.com/ruby-grape/grape/pull/1944): Reduces `attribute_translator` string allocations - [@ericproulx](https://github.com/ericproulx). diff --git a/README.md b/README.md index 0098f8c62..a723a8260 100644 --- a/README.md +++ b/README.md @@ -154,9 +154,8 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.3.0**. +You're reading the documentation for the stable release of Grape, **1.3.0**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.2.5](https://github.com/ruby-grape/grape/blob/v1.2.5/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 00faa5f52..7785e6be6 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.2.6' + VERSION = '1.3.0' end From 62b9f6cc33adcd67e1a4654693fb07b06ca51305 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 11 Jan 2020 10:29:46 +0200 Subject: [PATCH 170/290] Preparing for next development iteration, 1.3.1. --- CHANGELOG.md | 10 ++++++++++ README.md | 3 ++- lib/grape/version.rb | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8494be0e0..482ad0cf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.3.1 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.3.0 (2020/01/11) #### Features diff --git a/README.md b/README.md index a723a8260..b661b95d1 100644 --- a/README.md +++ b/README.md @@ -154,8 +154,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, **1.3.0**. +You're reading the documentation for the next release of Grape, which should be **1.3.1**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.3.0](https://github.com/ruby-grape/grape/blob/v1.3.0/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 7785e6be6..641165dd1 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.3.0' + VERSION = '1.3.1' end From 9ff085264c5ce3db12290c68bc8373578234e425 Mon Sep 17 00:00:00 2001 From: dm1try Date: Sat, 11 Jan 2020 22:24:15 +0300 Subject: [PATCH 171/290] fix args forwarding in Middleware::Stack#merge_with for ruby 2.7.0 ref #1967 --- CHANGELOG.md | 1 + lib/grape/middleware/stack.rb | 3 ++- spec/grape/middleware/stack_spec.rb | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 482ad0cf1..075076a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). ### 1.3.0 (2020/01/11) diff --git a/lib/grape/middleware/stack.rb b/lib/grape/middleware/stack.rb index 8509a4653..488a51498 100644 --- a/lib/grape/middleware/stack.rb +++ b/lib/grape/middleware/stack.rb @@ -78,7 +78,8 @@ def use(*args, &block) def merge_with(middleware_specs) middleware_specs.each do |operation, *args| if args.last.is_a?(Proc) - public_send(operation, *args, &args.pop) + last_proc = args.pop + public_send(operation, *args, &last_proc) else public_send(operation, *args) end diff --git a/spec/grape/middleware/stack_spec.rb b/spec/grape/middleware/stack_spec.rb index 7579dc1d0..e408ef9e8 100644 --- a/spec/grape/middleware/stack_spec.rb +++ b/spec/grape/middleware/stack_spec.rb @@ -111,6 +111,15 @@ def initialize(&block) expect(subject[1]).to eq(StackSpec::BlockMiddleware) expect(subject[2]).to eq(StackSpec::BarMiddleware) end + + context 'middleware spec with proc declaration exists' do + let(:middleware_spec_with_proc) { [:use, StackSpec::FooMiddleware, proc] } + + it 'properly forwards spec arguments' do + expect(subject).to receive(:use).with(StackSpec::FooMiddleware) + subject.merge_with([middleware_spec_with_proc]) + end + end end describe '#build' do From 341160c03d9ca7666c100452d9fce0b7c76ce9f5 Mon Sep 17 00:00:00 2001 From: Stuart <39475167+FlickStuart@users.noreply.github.com> Date: Wed, 15 Jan 2020 07:21:12 +1300 Subject: [PATCH 172/290] BigDecimal coercion (#1971) Mapping for BigDecimal to DryTypes::Coercible::Decimal for coercion --- CHANGELOG.md | 2 +- lib/grape/validations/types/primitive_coercer.rb | 6 ++++-- spec/grape/validations/validators/coerce_spec.rb | 13 +++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 075076a7f..13188bb37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ #### Fixes -* Your contribution here. +* [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). ### 1.3.0 (2020/01/11) diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index 0e4683396..393087ba3 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -12,11 +12,13 @@ class PrimitiveCoercer < DryTypeCoercer Grape::API::Boolean => DryTypes::Params::Bool, # unfortunatelly, a +Params+ scope doesn't contain String - String => DryTypes::Coercible::String + String => DryTypes::Coercible::String, + BigDecimal => DryTypes::Coercible::Decimal }.freeze STRICT_MAPPING = { - Grape::API::Boolean => DryTypes::Strict::Bool + Grape::API::Boolean => DryTypes::Strict::Bool, + BigDecimal => DryTypes::Strict::Decimal }.freeze def initialize(type, strict = false) diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 69655850f..d3c9ed7bf 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -154,6 +154,19 @@ def self.parsed?(value) end context 'coerces' do + it 'BigDecimal' do + subject.params do + requires :bigdecimal, coerce: BigDecimal + end + subject.get '/bigdecimal' do + params[:bigdecimal].class + end + + get '/bigdecimal', bigdecimal: '45' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('BigDecimal') + end + it 'Integer' do subject.params do requires :int, coerce: Integer From 020a1660bf6b48c4cbeb3e77b20bcf5845741dd0 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 14 Jan 2020 20:37:45 +0200 Subject: [PATCH 173/290] more tests for Grape::Validations::Types::PrimitiveCoercer Unit tests better focus on corner cases in this case than integration tests which might be too high level. --- .../validations/types/primitive_coercer.rb | 5 +- .../types/primitive_coercer_spec.rb | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 spec/grape/validations/types/primitive_coercer_spec.rb diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index 393087ba3..4205b72d0 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -6,12 +6,13 @@ module Grape module Validations module Types # Coerces the given value to a type defined via a +type+ argument during - # initialization. + # initialization. When +strict+ is true, it doesn't coerce a value but check + # that it has the proper type. class PrimitiveCoercer < DryTypeCoercer MAPPING = { Grape::API::Boolean => DryTypes::Params::Bool, - # unfortunatelly, a +Params+ scope doesn't contain String + # unfortunately, a +Params+ scope doesn't contain String String => DryTypes::Coercible::String, BigDecimal => DryTypes::Coercible::Decimal }.freeze diff --git a/spec/grape/validations/types/primitive_coercer_spec.rb b/spec/grape/validations/types/primitive_coercer_spec.rb new file mode 100644 index 000000000..987ab11ec --- /dev/null +++ b/spec/grape/validations/types/primitive_coercer_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Grape::Validations::Types::PrimitiveCoercer do + let(:strict) { false } + + subject { described_class.new(type, strict) } + + describe '.call' do + context 'Boolean' do + let(:type) { Grape::API::Boolean } + + it 'coerces to Boolean' do + expect(subject.call(0)).to eq(false) + end + end + + context 'String' do + let(:type) { String } + + it 'coerces to String' do + expect(subject.call(10)).to eq('10') + end + end + + context 'BigDecimal' do + let(:type) { BigDecimal } + + it 'coerces to BigDecimal' do + expect(subject.call(5)).to eq(BigDecimal(5)) + end + end + + context 'the strict mode' do + let(:strict) { true } + + context 'Boolean' do + let(:type) { Grape::API::Boolean } + + it 'returns an error when the given value is not Boolean' do + expect(subject.call(1)).to be_instance_of(Grape::Validations::Types::InvalidValue) + end + + it 'returns a value as it is when the given value is Boolean' do + expect(subject.call(true)).to eq(true) + end + end + + context 'BigDecimal' do + let(:type) { BigDecimal } + + it 'returns an error when the given value is not BigDecimal' do + expect(subject.call(1)).to be_instance_of(Grape::Validations::Types::InvalidValue) + end + + it 'returns a value as it is when the given value is BigDecimal' do + expect(subject.call(BigDecimal(0))).to eq(BigDecimal(0)) + end + end + end + end +end From 752bf4c0aa9b113c20b5c4de0b358f11752d46cb Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Wed, 15 Jan 2020 09:30:39 +0200 Subject: [PATCH 174/290] Ensure classes/modules listed for autoload really exist --- CHANGELOG.md | 1 + lib/grape.rb | 1 - spec/spec_helper.rb | 2 ++ spec/support/eager_load.rb | 19 +++++++++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 spec/support/eager_load.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 13188bb37..44924a61e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ #### Fixes +* [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk). * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). diff --git a/lib/grape.rb b/lib/grape.rb index 388fb54bd..e5fcc9802 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -84,7 +84,6 @@ module Extensions eager_autoload do autoload :DeepMergeableHash autoload :DeepSymbolizeHash - autoload :DeepHashWithIndifferentAccess autoload :Hash end module ActiveSupport diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index fa4f8bf00..18fae8d8e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,6 +14,8 @@ require file end +eager_load! + # The default value for this setting is true in a standard Rails app, # so it should be set to true here as well to reflect that. I18n.enforce_available_locales = true diff --git a/spec/support/eager_load.rb b/spec/support/eager_load.rb new file mode 100644 index 000000000..c55e4f243 --- /dev/null +++ b/spec/support/eager_load.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Grape uses autoload https://api.rubyonrails.org/classes/ActiveSupport/Autoload.html. +# When a class/module get added to the list, ActiveSupport doesn't check whether it really exists. +# This method loads all classes/modules defined via autoload to be sure only existing +# classes/modules were listed. +def eager_load!(scope = Grape) + # get modules + scope.constants.each do |const_name| + const = scope.const_get(const_name) + + next unless const.respond_to?(:eager_load!) + + const.eager_load! + + # check its modules, they might need to be loaded as well. + eager_load!(const) + end +end From 348a7c340f44e8f2bfbbda681add44e3d4dcee97 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Thu, 16 Jan 2020 20:25:22 +0200 Subject: [PATCH 175/290] skip validation for a file if it is optional and nil (#1977) --- CHANGELOG.md | 1 + lib/grape/validations/types/file.rb | 1 + spec/grape/validations_spec.rb | 14 +++++--------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44924a61e..e4df3db3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ #### Fixes +* [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk). * [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk). * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). diff --git a/lib/grape/validations/types/file.rb b/lib/grape/validations/types/file.rb index 2802ace48..6b79389de 100644 --- a/lib/grape/validations/types/file.rb +++ b/lib/grape/validations/types/file.rb @@ -8,6 +8,7 @@ module Types # this class is here only to assert that rack's handling has succeeded. class File def call(input) + return if input.nil? return InvalidValue.new unless coerced?(input) # Processing of multipart file objects diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 3e8df50ff..b1625d570 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -11,14 +11,17 @@ def app describe 'params' do context 'optional' do - it 'validates when params is present' do + before do subject.params do optional :a_number, regexp: /^[0-9]+$/ + optional :attachment, type: File end subject.get '/optional' do 'optional works!' end + end + it 'validates when params is present' do get '/optional', a_number: 'string' expect(last_response.status).to eq(400) expect(last_response.body).to eq('a_number is invalid') @@ -29,14 +32,7 @@ def app end it "doesn't validate when param not present" do - subject.params do - optional :a_number, regexp: /^[0-9]+$/ - end - subject.get '/optional' do - 'optional works!' - end - - get '/optional' + get '/optional', a_number: nil, attachment: nil expect(last_response.status).to eq(200) expect(last_response.body).to eq('optional works!') end From d3d767c7007172745576ccea7b1e7d50b532da23 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 18 Jan 2020 14:43:41 +0200 Subject: [PATCH 176/290] more unit tests for coercion --- .../types/primitive_coercer_spec.rb | 16 +++++- .../validations/validators/coerce_spec.rb | 53 +------------------ 2 files changed, 16 insertions(+), 53 deletions(-) diff --git a/spec/grape/validations/types/primitive_coercer_spec.rb b/spec/grape/validations/types/primitive_coercer_spec.rb index 987ab11ec..d215bf332 100644 --- a/spec/grape/validations/types/primitive_coercer_spec.rb +++ b/spec/grape/validations/types/primitive_coercer_spec.rb @@ -11,8 +11,20 @@ context 'Boolean' do let(:type) { Grape::API::Boolean } - it 'coerces to Boolean' do - expect(subject.call(0)).to eq(false) + [true, 'true', 1].each do |val| + it "coerces '#{val}' to true" do + expect(subject.call(val)).to eq(true) + end + end + + [false, 'false', 0].each do |val| + it "coerces '#{val}' to false" do + expect(subject.call(val)).to eq(false) + end + end + + it 'returns an error when the given value cannot be coerced' do + expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue) end end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index d3c9ed7bf..dbf57c9d5 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -294,66 +294,17 @@ def self.parsed?(value) end end - it 'Bool' do - subject.params do - requires :bool, coerce: Grape::API::Boolean - end - subject.get '/bool' do - params[:bool].class - end - - get '/bool', bool: 1 - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('TrueClass') - - get '/bool', bool: 0 - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('FalseClass') - - get '/bool', bool: 'false' - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('FalseClass') - - get '/bool', bool: 'true' - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('TrueClass') - end - it 'Boolean' do subject.params do - optional :boolean, type: Boolean, default: true + requires :boolean, type: Boolean end subject.get '/boolean' do params[:boolean].class end - get '/boolean' + get '/boolean', boolean: 1 expect(last_response.status).to eq(200) expect(last_response.body).to eq('TrueClass') - - get '/boolean', boolean: true - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('TrueClass') - - get '/boolean', boolean: false - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('FalseClass') - - get '/boolean', boolean: 'true' - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('TrueClass') - - get '/boolean', boolean: 'false' - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('FalseClass') - - get '/boolean', boolean: 123 - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('boolean is invalid') - - get '/boolean', boolean: '123' - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('boolean is invalid') end it 'Rack::Multipart::UploadedFile' do From 90cc14d59df650314c9f04e24c5240468f4dc4b9 Mon Sep 17 00:00:00 2001 From: ZeroInputCtrl Date: Tue, 4 Feb 2020 09:17:22 -0500 Subject: [PATCH 177/290] Re-add exactly_one_of mutually exclusive error message --- CHANGELOG.md | 1 + .../validations/validators/exactly_one_of.rb | 3 ++- .../exceptions/validation_errors_spec.rb | 4 ++-- .../validators/exactly_one_of_spec.rb | 24 +++++++++---------- spec/grape/validations_spec.rb | 6 ++--- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4df3db3f..10669345a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ #### Fixes +* [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl). * [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk). * [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk). * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index 8aa616ea6..20d5cdc1c 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -7,7 +7,8 @@ module Validations class ExactlyOneOfValidator < MultipleParamsBase def validate_params!(params) return if keys_in_common(params).length == 1 - raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys_in_common(params).length.zero? + raise Grape::Exceptions::Validation.new(params: keys_in_common(params), message: message(:mutual_exclusion)) end end end diff --git a/spec/grape/exceptions/validation_errors_spec.rb b/spec/grape/exceptions/validation_errors_spec.rb index eb75608b5..cf7e1e540 100644 --- a/spec/grape/exceptions/validation_errors_spec.rb +++ b/spec/grape/exceptions/validation_errors_spec.rb @@ -81,8 +81,8 @@ def app expect(last_response.status).to eq(400) expect(JSON.parse(last_response.body)).to eq( [ - 'params' => %w[beer wine juice], - 'messages' => ['are missing, exactly one parameter must be provided'] + 'params' => %w[beer wine], + 'messages' => ['are mutually exclusive'] ] ) end diff --git a/spec/grape/validations/validators/exactly_one_of_spec.rb b/spec/grape/validations/validators/exactly_one_of_spec.rb index 143921c11..87eba59d3 100644 --- a/spec/grape/validations/validators/exactly_one_of_spec.rb +++ b/spec/grape/validations/validators/exactly_one_of_spec.rb @@ -100,7 +100,7 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + 'beer,wine,grapefruit' => ['are mutually exclusive'] ) end @@ -112,7 +112,7 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + 'beer,wine,grapefruit' => ['are mutually exclusive'] ) end end @@ -126,7 +126,7 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'beer,wine,grapefruit' => ['are missing, exactly one parameter must be provided'] + 'beer,grapefruit' => ['are mutually exclusive'] ) end end @@ -139,7 +139,7 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'beer,wine,grapefruit' => ['you should choose one'] + 'beer,wine' => ['you should choose one'] ) end end @@ -175,7 +175,7 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided'] + 'item[beer],item[wine]' => ['are mutually exclusive'] ) end end @@ -190,7 +190,7 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'item[beer],item[wine],item[grapefruit]' => ['are missing, exactly one parameter must be provided'] + 'item[beer],item[wine]' => ['are mutually exclusive'] ) end end @@ -213,11 +213,11 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'items[0][beer],items[0][wine],items[0][grapefruit]' => [ - 'are missing, exactly one parameter must be provided' + 'items[0][beer],items[0][wine]' => [ + 'are mutually exclusive' ], - 'items[1][beer],items[1][wine],items[1][grapefruit]' => [ - 'are missing, exactly one parameter must be provided' + 'items[1][wine],items[1][grapefruit]' => [ + 'are mutually exclusive' ] ) end @@ -231,8 +231,8 @@ def app validate expect(last_response.status).to eq 400 expect(JSON.parse(last_response.body)).to eq( - 'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => [ - 'are missing, exactly one parameter must be provided' + 'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [ + 'are mutually exclusive' ] ) end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index b1625d570..e690ccb7d 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -1418,7 +1418,7 @@ def validate_param!(attr_name, params) it 'errors when two or more are present' do get '/custom_message/exactly_one_of', beer: 'string', wine: 'anotherstring' expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter is required' + expect(last_response.body).to eq 'beer, wine are missing, exactly one parameter is required' end end @@ -1437,7 +1437,7 @@ def validate_param!(attr_name, params) it 'errors when two or more are present' do get '/exactly_one_of', beer: 'string', wine: 'anotherstring' expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'beer, wine, juice are missing, exactly one parameter must be provided' + expect(last_response.body).to eq 'beer, wine are mutually exclusive' end end @@ -1477,7 +1477,7 @@ def validate_param!(attr_name, params) it 'errors when two or more are present' do get '/exactly_one_of_nested', nested: { beer_nested: 'string' }, nested2: [{ beer_nested2: 'string', wine_nested2: 'anotherstring' }] expect(last_response.status).to eq(400) - expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2], nested2[0][juice_nested2] are missing, exactly one parameter must be provided' + expect(last_response.body).to eq 'nested2[0][beer_nested2], nested2[0][wine_nested2] are mutually exclusive' end end end From ad417aaab42921c43282db785ed1d8e31f5e6065 Mon Sep 17 00:00:00 2001 From: ZeroInputCtrl Date: Tue, 4 Feb 2020 10:48:33 -0500 Subject: [PATCH 178/290] Reuse return from keys_in_common --- lib/grape/validations/validators/exactly_one_of.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/grape/validations/validators/exactly_one_of.rb b/lib/grape/validations/validators/exactly_one_of.rb index 20d5cdc1c..b8e4ecb9c 100644 --- a/lib/grape/validations/validators/exactly_one_of.rb +++ b/lib/grape/validations/validators/exactly_one_of.rb @@ -6,9 +6,10 @@ module Grape module Validations class ExactlyOneOfValidator < MultipleParamsBase def validate_params!(params) - return if keys_in_common(params).length == 1 - raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys_in_common(params).length.zero? - raise Grape::Exceptions::Validation.new(params: keys_in_common(params), message: message(:mutual_exclusion)) + keys = keys_in_common(params) + return if keys.length == 1 + raise Grape::Exceptions::Validation.new(params: all_keys, message: message(:exactly_one)) if keys.length.zero? + raise Grape::Exceptions::Validation.new(params: keys, message: message(:mutual_exclusion)) end end end From 918ad55cb462ee74c7a55822c7ccae50fc983a0d Mon Sep 17 00:00:00 2001 From: Hosseintoussi Date: Sat, 8 Feb 2020 17:21:20 +0100 Subject: [PATCH 179/290] refactored the full_messages method with this I'm getting rid of `full_message` method which in ruby 2.5 returns a formatted string of the error. https://ruby-doc.org/core-2.5.0/Exception.html#full_message-method --- CHANGELOG.md | 2 +- lib/grape/exceptions/base.rb | 4 ---- lib/grape/exceptions/validation_errors.rb | 23 +++++++++++------------ 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10669345a..e3f538873 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk). * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). - +* [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactored the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi). ### 1.3.0 (2020/01/11) #### Features diff --git a/lib/grape/exceptions/base.rb b/lib/grape/exceptions/base.rb index 7f60d90ea..9eeba9301 100644 --- a/lib/grape/exceptions/base.rb +++ b/lib/grape/exceptions/base.rb @@ -57,10 +57,6 @@ def translate_attributes(keys, **options) end.join(', ') end - def translate_attribute(key, **options) - translate("#{BASE_ATTRIBUTES_KEY}.#{key}", default: key, **options) - end - def translate_message(key, **options) case key when Symbol diff --git a/lib/grape/exceptions/validation_errors.rb b/lib/grape/exceptions/validation_errors.rb index 8123e0847..f8bf32e31 100644 --- a/lib/grape/exceptions/validation_errors.rb +++ b/lib/grape/exceptions/validation_errors.rb @@ -5,6 +5,9 @@ module Grape module Exceptions class ValidationErrors < Grape::Exceptions::Base + ERRORS_FORMAT_KEY = 'grape.errors.format' + DEFAULT_ERRORS_FORMAT = '%{attributes} %{message}' + include Enumerable attr_reader :errors @@ -41,21 +44,17 @@ def to_json(**_opts) end def full_messages - messages = map { |attributes, error| full_message(attributes, error) } + messages = map do |attributes, error| + I18n.t( + ERRORS_FORMAT_KEY, + default: DEFAULT_ERRORS_FORMAT, + attributes: translate_attributes(attributes), + message: error.message + ) + end messages.uniq! messages end - - private - - def full_message(attributes, error) - I18n.t( - 'grape.errors.format', - default: '%{attributes} %{message}', - attributes: attributes.count == 1 ? translate_attribute(attributes.first) : translate_attributes(attributes), - message: error.message - ) - end end end end From 2d5a6a8183c04ef6d5f48daf5419147545b98389 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Fri, 14 Feb 2020 11:14:51 +0100 Subject: [PATCH 180/290] Moved join in call_with_allow_headers. Instead of being joined by default it's joined when called Replaced 'OPTIONS' by actual Grape::Http::Headers::OPTIONS. --- CHANGELOG.md | 2 +- lib/grape/api/instance.rb | 2 +- lib/grape/router.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f538873..20df5dc6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * Your contribution here. #### Fixes - +* [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx). * [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl). * [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk). * [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 84e36d467..6844c08d1 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -227,7 +227,7 @@ def add_head_not_allowed_methods_and_options_methods allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET) end - allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods).join(', ') + allow_header = (self.class.namespace_inheritable(:do_not_route_options) ? allowed_methods : [Grape::Http::Headers::OPTIONS] | allowed_methods) unless self.class.namespace_inheritable(:do_not_route_options) || allowed_methods.include?(Grape::Http::Headers::OPTIONS) config[:endpoint].options[:options_route_enabled] = true diff --git a/lib/grape/router.rb b/lib/grape/router.rb index b30908842..26f20662a 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -106,7 +106,7 @@ def transaction(env) env, neighbor.allow_header, neighbor.endpoint - ) if neighbor && method == 'OPTIONS' && !cascade + ) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade route = match?(input, '*') return neighbor.endpoint.call(env) if neighbor && cascade && route @@ -160,7 +160,7 @@ def greedy_match?(input) end def call_with_allow_headers(env, methods, endpoint) - env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods + env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ') endpoint.call(env) end From edc718f9cb66fbd8582a0102ed5b9b95f8cbea3a Mon Sep 17 00:00:00 2001 From: Jaime Bellmyer Date: Tue, 18 Feb 2020 09:52:17 -0600 Subject: [PATCH 181/290] fix typo in README --- CHANGELOG.md | 1 + README.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20df5dc6a..bb110d91b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Your contribution here. #### Fixes +* [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer). * [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx). * [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl). * [#1977](https://github.com/ruby-grape/grape/pull/1977): Skip validation for a file if it is optional and nil - [@dnesteryuk](https://github.com/dnesteryuk). diff --git a/README.md b/README.md index b661b95d1..7231207ce 100644 --- a/README.md +++ b/README.md @@ -1723,7 +1723,7 @@ params do end ``` -Every validation will have it's own instance of the validator, which means that the validator can have a state. +Every validation will have its own instance of the validator, which means that the validator can have a state. ### Validation Errors @@ -3302,7 +3302,7 @@ end Blocks can be executed before or after every API call, using `before`, `after`, `before_validation` and `after_validation`. -If the API fails the `after` call will not be trigered, if you need code to execute for sure +If the API fails the `after` call will not be triggered, if you need code to execute for sure use the `finally`. Before and after callbacks execute in the following order: From 53d826fa95d2ed858b84b0e2b487a07f30876bdc Mon Sep 17 00:00:00 2001 From: Nathan Beyer Date: Tue, 25 Feb 2020 07:57:55 -0600 Subject: [PATCH 182/290] Address some Ruby warnings (#1995) * Address some Ruby warnings * Add warnings enablement to spec_helper.rb to see future warnings * Use instance_variable_defined? to clean up not initialized warnings * Add 'inherited' to NON_OVERRIDABLE list * Add multi_xml to test dependencies in Gemfile to address spec failure * Add changelog entry * expand changelog entry * adjust changelog * remove unnecessary multi_xml --- CHANGELOG.md | 3 +++ lib/grape/api.rb | 2 +- lib/grape/dsl/helpers.rb | 2 +- lib/grape/dsl/inside_route.rb | 17 +++++++++-------- lib/grape/dsl/parameters.rb | 8 ++++---- lib/grape/dsl/routing.rb | 4 +++- spec/grape/api_spec.rb | 13 +++++++------ .../validations/instance_behaivour_spec.rb | 2 +- spec/spec_helper.rb | 1 + 9 files changed, 30 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb110d91b..12e674d8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * Your contribution here. #### Fixes + +* [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: "undefined instance variables" and "method redefined" warnings - [@nbeyer](https://github.com/nbeyer). * [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer). * [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx). * [#1987](https://github.com/ruby-grape/grape/pull/1987): Re-add exactly_one_of mutually exclusive error message - [@ZeroInputCtrl](https://github.com/ZeroInputCtrl). @@ -13,6 +15,7 @@ * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). * [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactored the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi). + ### 1.3.0 (2020/01/11) #### Features diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 7df189d35..29f1c5786 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -8,7 +8,7 @@ module Grape # should subclass this class in order to build an API. class API # Class methods that we want to call on the API rather than on the API object - NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile!]).freeze + NON_OVERRIDABLE = (Class.new.methods + %i[call call! configuration compile! inherited]).freeze class << self attr_accessor :base_instance, :instances diff --git a/lib/grape/dsl/helpers.rb b/lib/grape/dsl/helpers.rb index 0d3c7e481..bbd2ed3ba 100644 --- a/lib/grape/dsl/helpers.rb +++ b/lib/grape/dsl/helpers.rb @@ -94,7 +94,7 @@ def api_changed(new_api) protected def process_named_params - return unless @named_params && @named_params.any? + return unless instance_variable_defined?(:@named_params) && @named_params && @named_params.any? api.namespace_stackable(:named_params, @named_params) end end diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 5943b1f73..9bd6f3516 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -182,12 +182,12 @@ def status(status = nil) when Integer @status = status when nil - return @status if @status + return @status if instance_variable_defined?(:@status) && @status case request.request_method.to_s.upcase when Grape::Http::Headers::POST 201 when Grape::Http::Headers::DELETE - if @body.present? + if instance_variable_defined?(:@body) && @body.present? 200 else 204 @@ -238,7 +238,7 @@ def body(value = nil) @body = '' status 204 else - @body + instance_variable_defined?(:@body) ? @body : nil end end @@ -272,7 +272,7 @@ def file(value = nil) warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.' @file = Grape::ServeFile::FileResponse.new(value) else - @file + instance_variable_defined?(:@file) ? @file : nil end end @@ -331,11 +331,12 @@ def present(*args) end representation = { root => representation } if root + if key - representation = (@body || {}).merge(key => representation) - elsif entity_class.present? && @body + representation = (body || {}).merge(key => representation) + elsif entity_class.present? && body raise ArgumentError, "Representation of type #{representation.class} cannot be merged." unless representation.respond_to?(:merge) - representation = @body.merge(representation) + representation = body.merge(representation) end body representation @@ -387,7 +388,7 @@ def entity_class_for_obj(object, options) def entity_representation_for(entity_class, object, options) embeds = { env: env } embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION] - entity_class.represent(object, **embeds.merge(options)) + entity_class.represent(object, embeds.merge(options)) end end end diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index bb5e4034a..9d393fd93 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -127,7 +127,7 @@ def requires(*attrs, &block) opts = attrs.extract_options!.clone opts[:presence] = { value: true, message: opts[:message] } - opts = @group.merge(opts) if @group + opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group if opts[:using] require_required_and_optional_fields(attrs.first, opts) @@ -146,7 +146,7 @@ def optional(*attrs, &block) opts = attrs.extract_options!.clone type = opts[:type] - opts = @group.merge(opts) if @group + opts = @group.merge(opts) if instance_variable_defined?(:@group) && @group # check type for optional parameter group if attrs && block_given? @@ -243,8 +243,8 @@ def map_params(params, element) # @return hash of parameters relevant for the current scope # @api private def params(params) - params = @parent.params(params) if @parent - params = map_params(params, @element) if @element + params = @parent.params(params) if instance_variable_defined?(:@parent) && @parent + params = map_params(params, @element) if instance_variable_defined?(:@element) && @element params end diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index ecbbb40ce..ac7e9e85c 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -51,7 +51,7 @@ def version(*args, &block) end end - @versions.last unless @versions.nil? + @versions.last if instance_variable_defined?(:@versions) && @versions end # Define a root URL prefix for your entire API. @@ -163,6 +163,8 @@ def route(methods, paths = ['/'], route_options = {}, &block) # end # end def namespace(space = nil, options = {}, &block) + @namespace_description = nil unless instance_variable_defined?(:@namespace_description) && @namespace_description + if space || block_given? within_namespace do previous_namespace_description = @namespace_description diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 108043d50..43abeffbe 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -935,7 +935,7 @@ class DummyFormatClass it 'adds a before filter to current and child namespaces only' do subject.get '/' do - "root - #{@foo}" + "root - #{instance_variable_defined?(:@foo) ? @foo : nil}" end subject.namespace :blah do before { @foo = 'foo' } @@ -3677,12 +3677,13 @@ def before end end context ':serializable_hash' do - before(:each) do - class SerializableHashExample - def serializable_hash - { abc: 'def' } - end + class SerializableHashExample + def serializable_hash + { abc: 'def' } end + end + + before(:each) do subject.format :serializable_hash end it 'instance' do diff --git a/spec/grape/validations/instance_behaivour_spec.rb b/spec/grape/validations/instance_behaivour_spec.rb index 9db070d95..9f2038dce 100644 --- a/spec/grape/validations/instance_behaivour_spec.rb +++ b/spec/grape/validations/instance_behaivour_spec.rb @@ -6,7 +6,7 @@ let(:validator_type) do Class.new(Grape::Validations::Base) do def validate_param!(_attr_name, _params) - if @instance_variable + if instance_variable_defined?(:@instance_variable) && @instance_variable raise Grape::Exceptions::Validation.new(params: ['params'], message: 'This should never happen') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 18fae8d8e..db3cf8b7b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,6 +35,7 @@ def read_chunks(body) config.include Spec::Support::Helpers config.raise_errors_for_deprecations! config.filter_run_when_matching :focus + config.warnings = true config.before(:each) { Grape::Util::InheritableSetting.reset_global! } From 002280415a46b1cabea565533141298781a48553 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 1 Mar 2020 22:56:08 +0100 Subject: [PATCH 183/290] Lazy lookup and lazy object (#2002) * mime_types middleware cache. * Replace %w by actual Supporter methods CONST * Replace %w by Supported methods CONST * Fixed [] leak. Its now generating empty array when needed. * Introducing lazy generated lookup tables (cache) and lazy_object. * Using Lazy_Object for request headers and Lazy lookup table when generating string. It reduced string allocations and lazy_object add a significant performance * Added CHANGELOG.md entry. * Change - to .freeze * keys.include? replaced by key?(name) --- CHANGELOG.md | 1 + lib/grape/api/instance.rb | 6 +-- lib/grape/dsl/inside_route.rb | 2 +- lib/grape/dsl/routing.rb | 6 +-- lib/grape/http/headers.rb | 25 +++++++++++++ lib/grape/middleware/base.rb | 4 +- lib/grape/namespace.rb | 14 ++++++- lib/grape/path.rb | 12 +++++- lib/grape/request.rb | 19 ++++++---- lib/grape/router.rb | 21 ++++++++--- lib/grape/router/pattern.rb | 33 +++++++++-------- lib/grape/util/base_inheritable.rb | 4 ++ lib/grape/util/cache.rb | 20 ++++++++++ lib/grape/util/lazy_object.rb | 43 ++++++++++++++++++++++ lib/grape/util/reverse_stackable_values.rb | 2 +- lib/grape/util/stackable_values.rb | 27 +++----------- lib/grape/validations/params_scope.rb | 2 +- 17 files changed, 176 insertions(+), 65 deletions(-) create mode 100644 lib/grape/util/cache.rb create mode 100644 lib/grape/util/lazy_object.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 12e674d8e..b11cfeef1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx). #### Fixes diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 6844c08d1..cc0793231 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -74,7 +74,7 @@ def call!(env) # (see #cascade?) def cascade(value = nil) if value.nil? - inheritable_setting.namespace_inheritable.keys.include?(:cascade) ? !namespace_inheritable(:cascade).nil? : true + inheritable_setting.namespace_inheritable.key?(:cascade) ? !namespace_inheritable(:cascade).nil? : true else namespace_inheritable(:cascade, value) end @@ -178,7 +178,7 @@ def call(env) # errors from reaching upstream. This is effectivelly done by unsetting # X-Cascade. Default :cascade is true. def cascade? - return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.keys.include?(:cascade) + return self.class.namespace_inheritable(:cascade) if self.class.inheritable_setting.namespace_inheritable.key?(:cascade) return self.class.namespace_inheritable(:version_options)[:cascade] if self.class.namespace_inheritable(:version_options) && self.class.namespace_inheritable(:version_options).key?(:cascade) true end @@ -209,7 +209,7 @@ def add_head_not_allowed_methods_and_options_methods route_settings[:endpoint] = route.app # using the :any shorthand produces [nil] for route methods, substitute all manually - route_settings[:methods] = %w[GET PUT POST DELETE PATCH HEAD OPTIONS] if route_settings[:methods].include?('*') + route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*') end end diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 9bd6f3516..3fa4d313b 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -177,7 +177,7 @@ def redirect(url, permanent: false, body: nil, **_options) def status(status = nil) case status when Symbol - raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.keys.include?(status) + raise ArgumentError, "Status code :#{status} is invalid." unless Rack::Utils::SYMBOL_TO_STATUS_CODE.key?(status) @status = Rack::Utils.status_code(status) when Integer @status = status diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index ac7e9e85c..b2f955cc0 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -142,11 +142,11 @@ def route(methods, paths = ['/'], route_options = {}, &block) reset_validations! end - %w[get post put head delete options patch].each do |meth| - define_method meth do |*args, &block| + Grape::Http::Headers::SUPPORTED_METHODS.each do |supported_method| + define_method supported_method.downcase do |*args, &block| options = args.extract_options! paths = args.first || ['/'] - route(meth.upcase, paths, options, &block) + route(supported_method, paths, options, &block) end end diff --git a/lib/grape/http/headers.rb b/lib/grape/http/headers.rb index 20419e131..4f45044cc 100644 --- a/lib/grape/http/headers.rb +++ b/lib/grape/http/headers.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'grape/util/lazy_object' + module Grape module Http module Headers @@ -27,6 +29,29 @@ module Headers FORMAT = 'format' + HTTP_HEADERS = Grape::Util::LazyObject.new do + common_http_headers = %w[ + Version + Host + Connection + Cache-Control + Dnt + Upgrade-Insecure-Requests + User-Agent + Sec-Fetch-Dest + Accept + Sec-Fetch-Site + Sec-Fetch-Mode + Sec-Fetch-User + Accept-Encoding + Accept-Language + Cookie + ].freeze + common_http_headers.each_with_object({}) do |header, response| + response["HTTP_#{header.upcase.tr('-', '_')}"] = header + end.freeze + end + def self.find_supported_method(route_method) Grape::Http::Headers::SUPPORTED_METHODS.detect { |supported_method| supported_method.casecmp(route_method).zero? } end diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index f3b18a92d..19be12d4c 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -74,11 +74,9 @@ def content_type end def mime_types - types_without_params = {} - content_types.each_pair do |k, v| + @mime_type ||= content_types.each_pair.with_object({}) do |(k, v), types_without_params| types_without_params[v.split(';').first] = k end - types_without_params end private diff --git a/lib/grape/namespace.rb b/lib/grape/namespace.rb index a15c460b6..d9de39b30 100644 --- a/lib/grape/namespace.rb +++ b/lib/grape/namespace.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'grape/util/cache' + module Grape # A container for endpoints or other namespaces, which allows for both # logical grouping of endpoints as well as sharing common configuration. @@ -25,13 +27,21 @@ def requirements # (see ::joined_space_path) def self.joined_space(settings) - (settings || []).map(&:space).join('/') + settings&.map(&:space) end # Join the namespaces from a list of settings to create a path prefix. # @param settings [Array] list of Grape::Util::InheritableSettings. def self.joined_space_path(settings) - Grape::Router.normalize_path(joined_space(settings)) + Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)]) + end + + class JoinedSpaceCache < Grape::Util::Cache + def initialize + @cache ||= Hash.new do |h, joined_space| + h[joined_space] = -joined_space.join('/') + end + end end end end diff --git a/lib/grape/path.rb b/lib/grape/path.rb index 6520d525f..001c81c3f 100644 --- a/lib/grape/path.rb +++ b/lib/grape/path.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'grape/util/cache' + module Grape # Represents a path to an endpoint. class Path @@ -58,7 +60,7 @@ def suffix end def path - Grape::Router.normalize_path(parts.join('/')) + Grape::Router.normalize_path(PartsCache[parts]) end def path_with_suffix @@ -71,6 +73,14 @@ def to_s private + class PartsCache < Grape::Util::Cache + def initialize + @cache ||= Hash.new do |h, parts| + h[parts] = -parts.join('/') + end + end + end + def parts parts = [mount_path, root_prefix].compact parts << ':version' if uses_path_versioning? diff --git a/lib/grape/request.rb b/lib/grape/request.rb index 79c65f475..62a6cd314 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'grape/util/lazy_object' + module Grape class Request < Rack::Request HTTP_PREFIX = 'HTTP_' @@ -30,14 +32,17 @@ def grape_routing_args end def build_headers - headers = {} - env.each_pair do |k, v| - next unless k.to_s.start_with? HTTP_PREFIX - - k = k[5..-1].split('_').each(&:capitalize!).join('-') - headers[k] = v + Grape::Util::LazyObject.new do + env.each_pair.with_object({}) do |(k, v), headers| + next unless k.to_s.start_with? HTTP_PREFIX + transformed_header = Grape::Http::Headers::HTTP_HEADERS[k] || transform_header(k) + headers[transformed_header] = v + end end - headers + end + + def transform_header(header) + -header[5..-1].split('_').each(&:capitalize!).join('-') end end end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 26f20662a..cdd3831e7 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'grape/router/route' +require 'grape/util/cache' module Grape class Router @@ -16,12 +17,20 @@ def initialize(pattern, regexp, index, **attributes) end end + class NormalizePathCache < Grape::Util::Cache + def initialize + @cache ||= Hash.new do |h, path| + normalized_path = +"/#{path}" + normalized_path.squeeze!('/') + normalized_path.sub!(%r{/+\Z}, '') + normalized_path = '/' if normalized_path.empty? + h[path] = -normalized_path + end + end + end + def self.normalize_path(path) - path = +"/#{path}" - path.squeeze!('/') - path.sub!(%r{/+\Z}, '') - path = '/' if path == '' - path + NormalizePathCache[path] end def self.supported_methods @@ -160,7 +169,7 @@ def greedy_match?(input) end def call_with_allow_headers(env, methods, endpoint) - env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ') + env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze endpoint.call(env) end diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index 258945c9d..15041fbe9 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -2,35 +2,32 @@ require 'forwardable' require 'mustermann/grape' +require 'grape/util/cache' module Grape class Router class Pattern - DEFAULT_PATTERN_OPTIONS = { uri_decode: true, type: :grape }.freeze + DEFAULT_PATTERN_OPTIONS = { uri_decode: true }.freeze DEFAULT_SUPPORTED_CAPTURE = %i[format version].freeze - attr_reader :origin, :path, :capture, :pattern + attr_reader :origin, :path, :pattern, :to_regexp extend Forwardable def_delegators :pattern, :named_captures, :params - def_delegators :@regexp, :=== + def_delegators :to_regexp, :=== alias match? === def initialize(pattern, **options) @origin = pattern @path = build_path(pattern, **options) - @capture = extract_capture(**options) - @pattern = Mustermann.new(@path, **pattern_options) - @regexp = to_regexp - end - - def to_regexp - @to_regexp ||= @pattern.to_regexp + @pattern = Mustermann::Grape.new(@path, **pattern_options(options)) + @to_regexp = @pattern.to_regexp end private - def pattern_options + def pattern_options(options) + capture = extract_capture(**options) options = DEFAULT_PATTERN_OPTIONS.dup options[:capture] = capture if capture.present? options @@ -43,23 +40,27 @@ def build_path(pattern, anchor: false, suffix: nil, **_options) pattern << '*path' end - pattern = pattern.split('/').tap do |parts| + pattern = -pattern.split('/').tap do |parts| parts[parts.length - 1] = '?' + parts.last end.join('/') if pattern.end_with?('*path') - "#{pattern}#{suffix}" + PatternCache[[pattern, suffix]] end def extract_capture(requirements: {}, **options) requirements = {}.merge(requirements) - supported_capture.each_with_object(requirements) do |field, capture| + DEFAULT_SUPPORTED_CAPTURE.each_with_object(requirements) do |field, capture| option = Array(options[field]) capture[field] = option.map(&:to_s) if option.present? end end - def supported_capture - DEFAULT_SUPPORTED_CAPTURE + class PatternCache < Grape::Util::Cache + def initialize + @cache ||= Hash.new do |h, (pattern, suffix)| + h[[pattern, suffix]] = -"#{pattern}#{suffix}" + end + end end end end diff --git a/lib/grape/util/base_inheritable.rb b/lib/grape/util/base_inheritable.rb index 9af28b182..f1f3378f1 100644 --- a/lib/grape/util/base_inheritable.rb +++ b/lib/grape/util/base_inheritable.rb @@ -31,6 +31,10 @@ def keys combined.uniq! combined end + + def key?(name) + inherited_values.key?(name) || new_values.key?(name) + end end end end diff --git a/lib/grape/util/cache.rb b/lib/grape/util/cache.rb new file mode 100644 index 000000000..3f51148f7 --- /dev/null +++ b/lib/grape/util/cache.rb @@ -0,0 +1,20 @@ +# frozen_String_literal: true + +require 'singleton' +require 'forwardable' + +module Grape + module Util + class Cache + include Singleton + + attr_reader :cache + + class << self + extend Forwardable + def_delegators :cache, :[] + def_delegators :instance, :cache + end + end + end +end diff --git a/lib/grape/util/lazy_object.rb b/lib/grape/util/lazy_object.rb new file mode 100644 index 000000000..22ec7c440 --- /dev/null +++ b/lib/grape/util/lazy_object.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# Based on https://github.com/HornsAndHooves/lazy_object + +module Grape + module Util + class LazyObject < BasicObject + attr_reader :callable + + def initialize(&callable) + @callable = callable + end + + def __target_object__ + @__target_object__ ||= callable.call + end + + def ==(other) + __target_object__ == other + end + + def !=(other) + __target_object__ != other + end + + def ! + !__target_object__ + end + + def method_missing(method_name, *args, &block) + if __target_object__.respond_to?(method_name) + __target_object__.send(method_name, *args, &block) + else + super + end + end + + def respond_to_missing?(method_name, include_priv = false) + __target_object__.respond_to?(method_name, include_priv) + end + end + end +end diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb index d8e8f160c..836039c45 100644 --- a/lib/grape/util/reverse_stackable_values.rb +++ b/lib/grape/util/reverse_stackable_values.rb @@ -9,7 +9,7 @@ class ReverseStackableValues < StackableValues def concat_values(inherited_value, new_value) [].tap do |value| - value.concat(new_value) + value.concat(new_value) if new_value value.concat(inherited_value) end end diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 79d3f3777..109c7fad2 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -5,30 +5,19 @@ module Grape module Util class StackableValues < BaseInheritable - attr_reader :frozen_values - - def initialize(*_args) - super - - @frozen_values = {} - end - # Even if there is no value, an empty array will be returned. def [](name) - return @frozen_values[name] if @frozen_values.key? name + inherited_value = inherited_values[name] + new_value = new_values[name] - inherited_value = @inherited_values[name] - new_value = @new_values[name] || [] - - return new_value unless inherited_value + return new_value || [] unless inherited_value concat_values(inherited_value, new_value) end def []=(name, value) - raise if @frozen_values.key? name - @new_values[name] ||= [] - @new_values[name].push value + new_values[name] ||= [] + new_values[name].push value end def to_hash @@ -37,16 +26,12 @@ def to_hash end end - def freeze_value(key) - @frozen_values[key] = self[key].freeze - end - protected def concat_values(inherited_value, new_value) [].tap do |value| value.concat(inherited_value) - value.concat(new_value) + value.concat(new_value) if new_value end end end diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index c8b1527c0..8a0c84cbb 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -244,7 +244,7 @@ def configure_declared_params end def validates(attrs, validations) - doc_attrs = { required: validations.keys.include?(:presence) } + doc_attrs = { required: validations.key?(:presence) } coerce_type = infer_coercion(validations) From 83f9ca7b3b9651ecaf485f942a1fabd801978fac Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 2 Mar 2020 19:44:18 +0100 Subject: [PATCH 184/290] Update rubocop (#2003) --- .rubocop.yml | 17 ++- .rubocop_todo.yml | 226 ++++++++++++++++++++++++++++++------ .travis.yml | 2 +- Appraisals | 8 ++ CHANGELOG.md | 1 + Gemfile | 3 +- gemfiles/multi_json.gemfile | 3 +- gemfiles/multi_xml.gemfile | 3 +- gemfiles/rack1.gemfile | 35 +++++- gemfiles/rack2-0.gemfile | 5 - gemfiles/rack2.gemfile | 38 ++++++ gemfiles/rack_edge.gemfile | 3 +- gemfiles/rails_5.gemfile | 3 +- gemfiles/rails_edge.gemfile | 3 +- 14 files changed, 294 insertions(+), 56 deletions(-) delete mode 100644 gemfiles/rack2-0.gemfile create mode 100644 gemfiles/rack2.gemfile diff --git a/.rubocop.yml b/.rubocop.yml index 995d9ed9d..f2d32fbfd 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,9 +1,8 @@ +require: + - rubocop-performance + AllCops: TargetRubyVersion: 2.4 - Include: - - Dangerfile - - gemfiles/*.gemfile - Exclude: - vendor/**/* - bin/**/* @@ -19,8 +18,14 @@ Style/MultilineIfModifier: Style/RaiseArgs: Enabled: false -Lint/UnneededDisable: - Enabled: false +Style/HashEachMethods: + Enabled: true + +Style/HashTransformKeys: + Enabled: true + +Style/HashTransformValues: + Enabled: true Metrics/BlockLength: Exclude: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f63d8ee2a..a8f218058 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,16 +1,42 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-12-09 18:07:42 +0100 using RuboCop version 0.51.0. +# on 2020-03-02 11:38:28 +0100 using RuboCop version 0.80.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. +# Offense count: 6 +# Cop supports --auto-correct. +Layout/ClosingHeredocIndentation: + Exclude: + - 'spec/grape/api_spec.rb' + - 'spec/grape/entity_spec.rb' + +# Offense count: 72 +# Cop supports --auto-correct. +Layout/EmptyLineAfterGuardClause: + Enabled: false + +# Offense count: 27 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. +# SupportedHashRocketStyles: key, separator, table +# SupportedColonStyles: key, separator, table +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Layout/HashAlignment: + Exclude: + - 'grape.gemspec' + - 'lib/grape/validations/params_scope.rb' + - 'lib/grape/validations/types/primitive_coercer.rb' + - 'spec/grape/api_spec.rb' + - 'spec/grape/entity_spec.rb' + # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent -Layout/IndentHeredoc: +# Configuration parameters: EnforcedStyle. +# SupportedStyles: squiggly, active_support, powerpack, unindent +Layout/HeredocIndentation: Exclude: - 'lib/grape/router/route.rb' - 'spec/grape/api_spec.rb' @@ -21,23 +47,45 @@ Lint/AmbiguousBlockAssociation: Exclude: - 'spec/grape/dsl/routing_spec.rb' +# Offense count: 4 +Lint/DisjunctiveAssignmentInConstructor: + Exclude: + - 'lib/grape/namespace.rb' + - 'lib/grape/path.rb' + - 'lib/grape/router.rb' + - 'lib/grape/router/pattern.rb' + # Offense count: 1 -Lint/RescueWithoutErrorClass: +# Cop supports --auto-correct. +Lint/NonDeterministicRequireOrder: Exclude: - - 'lib/grape/validations/validators/coerce.rb' + - 'spec/spec_helper.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Lint/RedundantCopDisableDirective: + Exclude: + - 'lib/grape/router/attribute_translator.rb' # Offense count: 1 # Cop supports --auto-correct. -Lint/UnneededRequireStatement: +Lint/RedundantRequireStatement: Exclude: - 'lib/grape.rb' -# Offense count: 46 +# Offense count: 2 +# Cop supports --auto-correct. +Lint/ToJSON: + Exclude: + - 'spec/grape/middleware/formatter_spec.rb' + +# Offense count: 48 Metrics/AbcSize: - Max: 44 + Max: 43 -# Offense count: 7 +# Offense count: 6 # Configuration parameters: CountComments, ExcludedMethods. +# ExcludedMethods: refine Metrics/BlockLength: Max: 182 @@ -46,18 +94,12 @@ Metrics/BlockLength: Metrics/ClassLength: Max: 305 -# Offense count: 30 +# Offense count: 32 Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 1300 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 215 - -# Offense count: 60 -# Configuration parameters: CountComments. +# Offense count: 61 +# Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: Max: 33 @@ -66,19 +108,41 @@ Metrics/MethodLength: Metrics/ModuleLength: Max: 220 -# Offense count: 22 +# Offense count: 25 Metrics/PerceivedComplexity: Max: 14 -# Offense count: 4 -# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. -# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS -Naming/FileName: +# Offense count: 3 +# Configuration parameters: EnforcedStyleForLeadingUnderscores. +# SupportedStylesForLeadingUnderscores: disallowed, required, optional +Naming/MemoizedInstanceVariableName: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/middleware/base.rb' + - 'spec/grape/integration/rack_spec.rb' + +# Offense count: 5 +# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. +# AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp +Naming/MethodParameterName: + Exclude: + - 'lib/grape/endpoint.rb' + - 'lib/grape/middleware/error.rb' + - 'lib/grape/middleware/stack.rb' + - 'spec/grape/api_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: PreferredName. +Naming/RescuedExceptionsVariableName: + Exclude: + - 'lib/grape/middleware/error.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Performance/InefficientHashSearch: Exclude: - - 'Appraisals' - - 'Gemfile' - - 'Guardfile' - - 'Rakefile' + - 'spec/grape/validations/validators/values_spec.rb' # Offense count: 1 # Cop supports --auto-correct. @@ -86,34 +150,122 @@ Performance/RegexpMatch: Exclude: - 'lib/grape/middleware/versioner/path.rb' +# Offense count: 5 +# Cop supports --auto-correct. +Style/EmptyLambdaParameter: + Exclude: + - 'spec/grape/dsl/callbacks_spec.rb' + - 'spec/grape/dsl/middleware_spec.rb' + - 'spec/grape/dsl/routing_spec.rb' + - 'spec/grape/middleware/auth/dsl_spec.rb' + - 'spec/grape/middleware/stack_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Style/ExpandPathArguments: + Exclude: + - 'grape.gemspec' + - 'lib/grape.rb' + - 'spec/grape/validations/validators/coerce_spec.rb' + # Offense count: 2 -# Configuration parameters: SupportedStyles. -# SupportedStyles: annotated, template +# Configuration parameters: . +# SupportedStyles: annotated, template, unannotated Style/FormatStringToken: EnforcedStyle: template # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +Style/HashTransformValues: + Exclude: + - 'lib/grape/api.rb' + +# Offense count: 23 +# Cop supports --auto-correct. +Style/IfUnlessModifier: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/dsl/desc.rb' + - 'lib/grape/dsl/request_response.rb' + - 'lib/grape/dsl/routing.rb' + - 'lib/grape/dsl/settings.rb' + - 'lib/grape/endpoint.rb' + - 'lib/grape/error_formatter/json.rb' + - 'lib/grape/error_formatter/xml.rb' + - 'lib/grape/middleware/error.rb' + - 'lib/grape/middleware/formatter.rb' + - 'lib/grape/middleware/versioner/accept_version_header.rb' + - 'lib/grape/validations/params_scope.rb' + - 'lib/grape/validations/validators/base.rb' + - 'lib/grape/validations/validators/default.rb' + - 'spec/support/versioned_helpers.rb' + +# Offense count: 1 +Style/MethodMissingSuper: + Exclude: + - 'lib/grape/router/attribute_translator.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: literals, strict +Style/MutableConstant: + Exclude: + - 'lib/grape/middleware/versioner/header.rb' + - 'lib/grape/router/route.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - 'spec/**/*' - 'lib/grape/middleware/formatter.rb' -# Offense count: 12 +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: implicit, explicit +Style/RescueStandardError: + Exclude: + - 'lib/grape/validations/validators/coerce.rb' + +# Offense count: 11 # Cop supports --auto-correct. -# Configuration parameters: ConvertCodeThatCanStartToReturnNil. +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: Exclude: - 'lib/grape/api/instance.rb' - 'lib/grape/dsl/desc.rb' - - 'lib/grape/dsl/helpers.rb' - 'lib/grape/dsl/inside_route.rb' - - 'lib/grape/dsl/routing.rb' + - 'lib/grape/dsl/request_response.rb' - 'lib/grape/endpoint.rb' - 'lib/grape/middleware/error.rb' - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/middleware/versioner/header.rb' - - 'lib/grape/middleware/versioner/param.rb' - - 'lib/grape/middleware/versioner/path.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInHashLiteral: + Exclude: + - 'lib/grape/middleware/error.rb' + +# Offense count: 10 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, MinSize, WordRegex. +# SupportedStyles: percent, brackets +Style/WordArray: + Exclude: + - 'spec/grape/validations/validators/except_values_spec.rb' + - 'spec/grape/validations/validators/values_spec.rb' + +# Offense count: 1314 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 215 diff --git a/.travis.yml b/.travis.yml index 491fe4464..66e723b47 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ matrix: - rvm: 2.7.0 gemfile: gemfiles/rack1.gemfile - rvm: 2.7.0 - gemfile: gemfiles/rack2-0.gemfile + gemfile: gemfiles/rack2.gemfile - rvm: 2.7.0 gemfile: gemfiles/rack_edge.gemfile - rvm: 2.7.0 diff --git a/Appraisals b/Appraisals index a779aab25..e9c0526e0 100644 --- a/Appraisals +++ b/Appraisals @@ -19,3 +19,11 @@ end appraise 'multi_xml' do gem 'multi_xml', require: 'multi_xml' end + +appraise 'rack1' do + gem 'rack', '~> 1.0' +end + +appraise 'rack2' do + gem 'rack', '~> 2.0' +end diff --git a/CHANGELOG.md b/CHANGELOG.md index b11cfeef1..11bc0456d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#2003](https://github.com/ruby-grape/grape/pull/2003): Upgraded Rubocop to 0.80.1 - [@ericproulx](https://github.com/ericproulx). * [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx). #### Fixes diff --git a/Gemfile b/Gemfile index 0469596f9..ecd0b7e0f 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,8 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.51.0' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false end group :development do diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index a76b25ac2..ed8a87bc9 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -10,7 +10,8 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.51.0' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false end group :development do diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index 0c520d1f7..e8598bd36 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -10,7 +10,8 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.51.0' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false end group :development do diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile index 362af946f..677bff97f 100644 --- a/gemfiles/rack1.gemfile +++ b/gemfiles/rack1.gemfile @@ -1,5 +1,38 @@ # frozen_string_literal: true -eval_gemfile('../Gemfile') +# This file was generated by Appraisal + +source 'https://rubygems.org' gem 'rack', '~> 1.0' + +group :development, :test do + gem 'bundler' + gem 'hashie' + gem 'rake' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false +end + +group :development do + gem 'appraisal' + gem 'benchmark-ips' + gem 'guard' + gem 'guard-rspec' + gem 'guard-rubocop' +end + +group :test do + gem 'cookiejar' + gem 'coveralls_reborn' + gem 'danger-toc', '~> 0.1.2' + gem 'grape-entity', '~> 0.6' + gem 'maruku' + gem 'mime-types' + gem 'rack-jsonp', require: 'rack/jsonp' + gem 'rack-test', '~> 1.1.0' + gem 'rspec', '~> 3.0' + gem 'ruby-grape-danger', '~> 0.1.0', require: false +end + +gemspec path: '../' diff --git a/gemfiles/rack2-0.gemfile b/gemfiles/rack2-0.gemfile deleted file mode 100644 index 362af946f..000000000 --- a/gemfiles/rack2-0.gemfile +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -eval_gemfile('../Gemfile') - -gem 'rack', '~> 1.0' diff --git a/gemfiles/rack2.gemfile b/gemfiles/rack2.gemfile new file mode 100644 index 000000000..4796d30a3 --- /dev/null +++ b/gemfiles/rack2.gemfile @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source 'https://rubygems.org' + +gem 'rack', '~> 2.0' + +group :development, :test do + gem 'bundler' + gem 'hashie' + gem 'rake' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false +end + +group :development do + gem 'appraisal' + gem 'benchmark-ips' + gem 'guard' + gem 'guard-rspec' + gem 'guard-rubocop' +end + +group :test do + gem 'cookiejar' + gem 'coveralls_reborn' + gem 'danger-toc', '~> 0.1.2' + gem 'grape-entity', '~> 0.6' + gem 'maruku' + gem 'mime-types' + gem 'rack-jsonp', require: 'rack/jsonp' + gem 'rack-test', '~> 1.1.0' + gem 'rspec', '~> 3.0' + gem 'ruby-grape-danger', '~> 0.1.0', require: false +end + +gemspec path: '../' diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index 3e4eddf8f..9269e3ae5 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -10,7 +10,8 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.51.0' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false end group :development do diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index c05f03ee8..493f94e3b 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -10,7 +10,8 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.51.0' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false end group :development do diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index 3713d04fc..295553ac7 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -10,7 +10,8 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.51.0' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false end group :development do From fbeedb6072410f389464ff9a303847852c16d817 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.) @dblockdotorg" Date: Thu, 5 Mar 2020 11:14:05 -0500 Subject: [PATCH 185/290] Content types registrable (#2005) --- CHANGELOG.md | 1 + lib/grape.rb | 3 ++- lib/grape/content_types.rb | 34 +++++++++++++++++++++++++ lib/grape/util/content_types.rb | 28 -------------------- spec/grape/middleware/formatter_spec.rb | 4 +-- 5 files changed, 39 insertions(+), 31 deletions(-) create mode 100644 lib/grape/content_types.rb delete mode 100644 lib/grape/util/content_types.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 11bc0456d..a65afb9e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#2005](https://github.com/ruby-grape/grape/pull/2005): Content types registrable - [@ericproulx](https://github.com/ericproulx). * [#2003](https://github.com/ruby-grape/grape/pull/2003): Upgraded Rubocop to 0.80.1 - [@ericproulx](https://github.com/ericproulx). * [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape.rb b/lib/grape.rb index e5fcc9802..125a2b4c6 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -218,7 +218,8 @@ module ServeFile end require 'grape/config' -require 'grape/util/content_types' +require 'grape/content_types' + require 'grape/util/lazy_value' require 'grape/util/lazy_block' require 'grape/util/endpoint_configuration' diff --git a/lib/grape/content_types.rb b/lib/grape/content_types.rb new file mode 100644 index 000000000..2c19f9731 --- /dev/null +++ b/lib/grape/content_types.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'grape/util/registrable' + +module Grape + module ContentTypes + extend Util::Registrable + + # Content types are listed in order of preference. + CONTENT_TYPES = { + xml: 'application/xml', + serializable_hash: 'application/json', + json: 'application/json', + binary: 'application/octet-stream', + txt: 'text/plain' + }.freeze + + class << self + def content_types_for_settings(settings) + return if settings.blank? + + settings.each_with_object({}) { |value, result| result.merge!(value) } + end + + def content_types_for(from_settings) + if from_settings.present? + from_settings + else + Grape::ContentTypes::CONTENT_TYPES.merge(default_elements) + end + end + end + end +end diff --git a/lib/grape/util/content_types.rb b/lib/grape/util/content_types.rb deleted file mode 100644 index df5a7b008..000000000 --- a/lib/grape/util/content_types.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Grape - module ContentTypes - # Content types are listed in order of preference. - CONTENT_TYPES = { # rubocop:disable Style/MutableConstant - xml: 'application/xml', - serializable_hash: 'application/json', - json: 'application/json', - binary: 'application/octet-stream', - txt: 'text/plain' - } - - def self.content_types_for_settings(settings) - return if settings.blank? - - settings.each_with_object({}) { |value, result| result.merge!(value) } - end - - def self.content_types_for(from_settings) - if from_settings.present? - from_settings - else - Grape::ContentTypes::CONTENT_TYPES - end - end - end -end diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index 102bca8f1..7c73663b5 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -402,10 +402,10 @@ def self.call(_, _) let(:app) { ->(_env) { [200, {}, ['']] } } before do Grape::Formatter.register :invalid, InvalidFormatter - Grape::ContentTypes::CONTENT_TYPES[:invalid] = 'application/x-invalid' + Grape::ContentTypes.register :invalid, 'application/x-invalid' end after do - Grape::ContentTypes::CONTENT_TYPES.delete(:invalid) + Grape::ContentTypes.default_elements.delete(:invalid) Grape::Formatter.default_elements.delete(:invalid) end From cd813382d17c16598c8d759ff04903df9f515627 Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.) @dblockdotorg" Date: Thu, 5 Mar 2020 14:48:59 -0500 Subject: [PATCH 186/290] Rubocop todo fixes (#2004) * Fix Style/HashTransformValues * Fix Lint/RedundantRequireStatement * Fix Lint/RedundantRequireStatement * Fix Style/MutableConstant * Fix Lint/DisjunctiveAssignmentInConstructor --- .rubocop_todo.yml | 35 ------------------------ CHANGELOG.md | 2 +- lib/grape.rb | 1 - lib/grape/api.rb | 2 +- lib/grape/middleware/versioner/header.rb | 6 ++-- lib/grape/middleware/versioner/path.rb | 2 +- lib/grape/namespace.rb | 2 +- lib/grape/path.rb | 2 +- lib/grape/router.rb | 2 +- lib/grape/router/pattern.rb | 2 +- lib/grape/router/route.rb | 4 +-- 11 files changed, 12 insertions(+), 48 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a8f218058..f2c4e9a38 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -47,14 +47,6 @@ Lint/AmbiguousBlockAssociation: Exclude: - 'spec/grape/dsl/routing_spec.rb' -# Offense count: 4 -Lint/DisjunctiveAssignmentInConstructor: - Exclude: - - 'lib/grape/namespace.rb' - - 'lib/grape/path.rb' - - 'lib/grape/router.rb' - - 'lib/grape/router/pattern.rb' - # Offense count: 1 # Cop supports --auto-correct. Lint/NonDeterministicRequireOrder: @@ -67,12 +59,6 @@ Lint/RedundantCopDisableDirective: Exclude: - 'lib/grape/router/attribute_translator.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Lint/RedundantRequireStatement: - Exclude: - - 'lib/grape.rb' - # Offense count: 2 # Cop supports --auto-correct. Lint/ToJSON: @@ -144,12 +130,6 @@ Performance/InefficientHashSearch: Exclude: - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Performance/RegexpMatch: - Exclude: - - 'lib/grape/middleware/versioner/path.rb' - # Offense count: 5 # Cop supports --auto-correct. Style/EmptyLambdaParameter: @@ -174,12 +154,6 @@ Style/ExpandPathArguments: Style/FormatStringToken: EnforcedStyle: template -# Offense count: 1 -# Cop supports --auto-correct. -Style/HashTransformValues: - Exclude: - - 'lib/grape/api.rb' - # Offense count: 23 # Cop supports --auto-correct. Style/IfUnlessModifier: @@ -205,15 +179,6 @@ Style/MethodMissingSuper: Exclude: - 'lib/grape/router/attribute_translator.rb' -# Offense count: 5 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Exclude: - - 'lib/grape/middleware/versioner/header.rb' - - 'lib/grape/router/route.rb' - # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. diff --git a/CHANGELOG.md b/CHANGELOG.md index a65afb9e3..bd4a93087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ * [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx). #### Fixes - +* [#2004](https://github.com/ruby-grape/grape/pull/2004): Rubocop fixes - [@ericproulx](https://github.com/ericproulx). * [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: "undefined instance variables" and "method redefined" warnings - [@nbeyer](https://github.com/nbeyer). * [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer). * [#1993](https://github.com/ruby-grape/grape/pull/1993): Lazy join allow header - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape.rb b/lib/grape.rb index 125a2b4c6..3ba532fbc 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -20,7 +20,6 @@ require 'active_support/dependencies/autoload' require 'active_support/notifications' require 'i18n' -require 'thread' I18n.load_path << File.expand_path('../grape/locale/en.yml', __FILE__) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 29f1c5786..507b22187 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -175,7 +175,7 @@ def evaluate_arguments(configuration, *args) if argument.respond_to?(:lazy?) && argument.lazy? argument.evaluate_from(configuration) elsif argument.is_a?(Hash) - argument.map { |key, value| [key, evaluate_arguments(configuration, value).first] }.to_h + argument.transform_values { |value| evaluate_arguments(configuration, value).first } elsif argument.is_a?(Array) evaluate_arguments(configuration, *argument) else diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index 0d0bcf2da..a87980d71 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -26,10 +26,10 @@ module Versioner # route. class Header < Base VENDOR_VERSION_HEADER_REGEX = - /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/ + /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))?(?:\+([a-z0-9*\-.]+))?\z/.freeze - HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/ - HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/ + HAS_VENDOR_REGEX = /\Avnd\.[a-z0-9.\-_!#\$&\^]+/.freeze + HAS_VERSION_REGEX = /\Avnd\.([a-z0-9.\-_!#\$&\^]+?)(?:-([a-z0-9*.]+))+/.freeze def before strict_header_checks if strict? diff --git a/lib/grape/middleware/versioner/path.rb b/lib/grape/middleware/versioner/path.rb index 80c3a1fd9..b7becc749 100644 --- a/lib/grape/middleware/versioner/path.rb +++ b/lib/grape/middleware/versioner/path.rb @@ -36,7 +36,7 @@ def before pieces = path.split('/') potential_version = pieces[1] - return unless potential_version =~ options[:pattern] + return unless potential_version&.match?(options[:pattern]) throw :error, status: 404, message: '404 API Version Not Found' if options[:versions] && !options[:versions].find { |v| v.to_s == potential_version } env[Grape::Env::API_VERSION] = potential_version end diff --git a/lib/grape/namespace.rb b/lib/grape/namespace.rb index d9de39b30..3473d3efb 100644 --- a/lib/grape/namespace.rb +++ b/lib/grape/namespace.rb @@ -38,7 +38,7 @@ def self.joined_space_path(settings) class JoinedSpaceCache < Grape::Util::Cache def initialize - @cache ||= Hash.new do |h, joined_space| + @cache = Hash.new do |h, joined_space| h[joined_space] = -joined_space.join('/') end end diff --git a/lib/grape/path.rb b/lib/grape/path.rb index 001c81c3f..b0be345bd 100644 --- a/lib/grape/path.rb +++ b/lib/grape/path.rb @@ -75,7 +75,7 @@ def to_s class PartsCache < Grape::Util::Cache def initialize - @cache ||= Hash.new do |h, parts| + @cache = Hash.new do |h, parts| h[parts] = -parts.join('/') end end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index cdd3831e7..192858cb6 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -19,7 +19,7 @@ def initialize(pattern, regexp, index, **attributes) class NormalizePathCache < Grape::Util::Cache def initialize - @cache ||= Hash.new do |h, path| + @cache = Hash.new do |h, path| normalized_path = +"/#{path}" normalized_path.squeeze!('/') normalized_path.sub!(%r{/+\Z}, '') diff --git a/lib/grape/router/pattern.rb b/lib/grape/router/pattern.rb index 15041fbe9..e8c108ad8 100644 --- a/lib/grape/router/pattern.rb +++ b/lib/grape/router/pattern.rb @@ -57,7 +57,7 @@ def extract_capture(requirements: {}, **options) class PatternCache < Grape::Util::Cache def initialize - @cache ||= Hash.new do |h, (pattern, suffix)| + @cache = Hash.new do |h, (pattern, suffix)| h[[pattern, suffix]] = -"#{pattern}#{suffix}" end end diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index d0b3e4290..62e470690 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -8,8 +8,8 @@ module Grape class Router class Route - ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/ - SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/ + ROUTE_ATTRIBUTE_REGEXP = /route_([_a-zA-Z]\w*)/.freeze + SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze FIXED_NAMED_CAPTURES = %w[format version].freeze attr_accessor :pattern, :translator, :app, :index, :regexp, :options From 960c7a7f015d55b438a5ddedd2a96f7ddbcf6833 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Fri, 6 Mar 2020 18:00:47 +0100 Subject: [PATCH 187/290] Upgrade coerce behavior when rescuing error. (#2006) Fix Style/RescueStandardError --- .rubocop_todo.yml | 8 -------- CHANGELOG.md | 1 + lib/grape/validations/validators/coerce.rb | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f2c4e9a38..b5140c65d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -188,14 +188,6 @@ Style/NumericPredicate: - 'spec/**/*' - 'lib/grape/middleware/formatter.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'lib/grape/validations/validators/coerce.rb' - # Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. diff --git a/CHANGELOG.md b/CHANGELOG.md index bd4a93087..a2e26777e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx). #### Fixes +* [#2006](https://github.com/ruby-grape/grape/pull/2006): Fix explicit rescue StandardError - [@ericproulx](https://github.com/ericproulx). * [#2004](https://github.com/ruby-grape/grape/pull/2004): Rubocop fixes - [@ericproulx](https://github.com/ericproulx). * [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: "undefined instance variables" and "method redefined" warnings - [@nbeyer](https://github.com/nbeyer). * [#1994](https://github.com/ruby-grape/grape/pull/1993): Fix typos in README - [@bellmyer](https://github.com/bellmyer). diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index c104d69e4..561ec1bcf 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -76,7 +76,7 @@ def coerce_value(val) converter.call(val) # Some custom types might fail, so it should be treated as an invalid value - rescue + rescue StandardError Types::InvalidValue.new end From 812284fd9334fbd07336e2ac095847cb1e87b866 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Wed, 11 Mar 2020 21:37:29 +0200 Subject: [PATCH 188/290] Preparing for release, 1.3.1 --- CHANGELOG.md | 4 ++-- README.md | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2e26777e..883e0fc26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,13 @@ -### 1.3.1 (Next) +### 1.3.1 (2020/03/11) #### Features -* Your contribution here. * [#2005](https://github.com/ruby-grape/grape/pull/2005): Content types registrable - [@ericproulx](https://github.com/ericproulx). * [#2003](https://github.com/ruby-grape/grape/pull/2003): Upgraded Rubocop to 0.80.1 - [@ericproulx](https://github.com/ericproulx). * [#2002](https://github.com/ruby-grape/grape/pull/2002): Objects allocation optimization (lazy_lookup) - [@ericproulx](https://github.com/ericproulx). #### Fixes + * [#2006](https://github.com/ruby-grape/grape/pull/2006): Fix explicit rescue StandardError - [@ericproulx](https://github.com/ericproulx). * [#2004](https://github.com/ruby-grape/grape/pull/2004): Rubocop fixes - [@ericproulx](https://github.com/ericproulx). * [#1995](https://github.com/ruby-grape/grape/pull/1995): Fix: "undefined instance variables" and "method redefined" warnings - [@nbeyer](https://github.com/nbeyer). diff --git a/README.md b/README.md index 7231207ce..7b9c88b84 100644 --- a/README.md +++ b/README.md @@ -154,9 +154,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.3.1**. -Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.3.0](https://github.com/ruby-grape/grape/blob/v1.3.0/README.md). +You're reading the documentation for the stable release of Grape, **1.3.1**. ## Project Resources From 7eec4ed50cc35df205c143e8581b612e64a39515 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Wed, 11 Mar 2020 21:41:33 +0200 Subject: [PATCH 189/290] Preparing for next development iteration, 1.3.2 --- CHANGELOG.md | 10 ++++++++++ README.md | 4 +++- lib/grape/version.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 883e0fc26..33e653908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.3.2 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.3.1 (2020/03/11) #### Features diff --git a/README.md b/README.md index 7b9c88b84..004c48b2c 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, **1.3.1**. +You're reading the documentation for the next release of Grape, which should be **1.3.2**. +Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.3.1](https://github.com/ruby-grape/grape/blob/v1.3.1/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 641165dd1..260d092ab 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.3.1' + VERSION = '1.3.2' end From 6d608cef50a1b653bcfbb7adb216a5bb932675eb Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 15 Mar 2020 15:34:15 +0100 Subject: [PATCH 190/290] Remove route.regexp and Any regexp since regexes are union. We don't need to keep the regexes in memory. Add CHANGELOG.md --- CHANGELOG.md | 2 +- lib/grape/router.rb | 15 ++++++++------- lib/grape/router/route.rb | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33e653908..6f67a11da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ #### Features -* Your contribution here. +* [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx). #### Fixes diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 192858cb6..d8428e426 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -8,10 +8,9 @@ class Router attr_reader :map, :compiled class Any < AttributeTranslator - attr_reader :pattern, :regexp, :index - def initialize(pattern, regexp, index, **attributes) + attr_reader :pattern, :index + def initialize(pattern, index, **attributes) @pattern = pattern - @regexp = regexp @index = index super(attributes) end @@ -39,18 +38,20 @@ def self.supported_methods def initialize @neutral_map = [] + @neutral_regexes = [] @map = Hash.new { |hash, key| hash[key] = [] } @optimized_map = Hash.new { |hash, key| hash[key] = // } end def compile! return if compiled - @union = Regexp.union(@neutral_map.map(&:regexp)) + @union = Regexp.union(@neutral_regexes) + @neutral_regexes = nil self.class.supported_methods.each do |method| routes = map[method] @optimized_map[method] = routes.map.with_index do |route, index| route.index = index - route.regexp = Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})") + Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})") end @optimized_map[method] = Regexp.union(@optimized_map[method]) end @@ -62,8 +63,8 @@ def append(route) end def associate_routes(pattern, **options) - regexp = Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}") - @neutral_map << Any.new(pattern, regexp, @neutral_map.length, **options) + @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}") + @neutral_map << Any.new(pattern, @neutral_map.length, **options) end def call(env) diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 62e470690..66333a5ab 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -12,7 +12,7 @@ class Route SOURCE_LOCATION_REGEXP = /^(.*?):(\d+?)(?::in `.+?')?$/.freeze FIXED_NAMED_CAPTURES = %w[format version].freeze - attr_accessor :pattern, :translator, :app, :index, :regexp, :options + attr_accessor :pattern, :translator, :app, :index, :options alias attributes translator From 3e5a953de9748d3bafaff3b420204b0b758b7aa4 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 16 Mar 2020 12:20:21 +0100 Subject: [PATCH 191/290] Create a new array only when needed. Add CHANGELOG.md --- CHANGELOG.md | 3 ++- lib/grape/util/reverse_stackable_values.rb | 4 +++- lib/grape/util/stackable_values.rb | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f67a11da..0f6537b17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ### 1.3.2 (Next) #### Features - +* Your contribution here. +* [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx). * [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx). #### Fixes diff --git a/lib/grape/util/reverse_stackable_values.rb b/lib/grape/util/reverse_stackable_values.rb index 836039c45..171f390f7 100644 --- a/lib/grape/util/reverse_stackable_values.rb +++ b/lib/grape/util/reverse_stackable_values.rb @@ -8,8 +8,10 @@ class ReverseStackableValues < StackableValues protected def concat_values(inherited_value, new_value) + return inherited_value unless new_value + [].tap do |value| - value.concat(new_value) if new_value + value.concat(new_value) value.concat(inherited_value) end end diff --git a/lib/grape/util/stackable_values.rb b/lib/grape/util/stackable_values.rb index 109c7fad2..01a568196 100644 --- a/lib/grape/util/stackable_values.rb +++ b/lib/grape/util/stackable_values.rb @@ -29,9 +29,11 @@ def to_hash protected def concat_values(inherited_value, new_value) + return inherited_value unless new_value + [].tap do |value| value.concat(inherited_value) - value.concat(new_value) if new_value + value.concat(new_value) end end end From b37025c108b1320a9e19cabbee8582e24b06aeba Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 16 Mar 2020 14:40:36 +0100 Subject: [PATCH 192/290] Replace =~ or match by match? when we don't need the MatchData object. match? function is new from 2.4 Add CHANGELOG.md --- CHANGELOG.md | 1 + lib/grape/middleware/versioner/header.rb | 2 +- lib/grape/middleware/versioner/parse_media_type_patch.rb | 3 ++- lib/grape/path.rb | 4 ++-- lib/grape/router/route.rb | 3 +-- lib/grape/validations/types/json.rb | 2 +- lib/grape/validations/validators/regexp.rb | 2 +- spec/grape/path_spec.rb | 8 ++++---- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f6537b17..1203fafc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Features * Your contribution here. +* [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx). * [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx). * [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape/middleware/versioner/header.rb b/lib/grape/middleware/versioner/header.rb index a87980d71..bb5fc1671 100644 --- a/lib/grape/middleware/versioner/header.rb +++ b/lib/grape/middleware/versioner/header.rb @@ -63,7 +63,7 @@ def strict_version_vendor_accept_header_presence_check def an_accept_header_with_version_and_vendor_is_present? header.qvalues.keys.any? do |h| - VENDOR_VERSION_HEADER_REGEX =~ h.sub('application/', '') + VENDOR_VERSION_HEADER_REGEX.match?(h.sub('application/', '')) end end diff --git a/lib/grape/middleware/versioner/parse_media_type_patch.rb b/lib/grape/middleware/versioner/parse_media_type_patch.rb index 40c72b6bf..ca7e0671c 100644 --- a/lib/grape/middleware/versioner/parse_media_type_patch.rb +++ b/lib/grape/middleware/versioner/parse_media_type_patch.rb @@ -3,11 +3,12 @@ module Rack module Accept module Header + ALLOWED_CHARACTERS = %r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$}.freeze class << self # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44 def parse_media_type(media_type) # see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names - m = media_type.to_s.match(%r{^([a-z*]+)\/([a-z0-9*\&\^\-_#\$!.+]+)(?:;([a-z0-9=;]+))?$}) + m = media_type.match(ALLOWED_CHARACTERS) m ? [m[1], m[2], m[3] || ''] : [] end end diff --git a/lib/grape/path.rb b/lib/grape/path.rb index b0be345bd..e36684627 100644 --- a/lib/grape/path.rb +++ b/lib/grape/path.rb @@ -42,11 +42,11 @@ def uses_path_versioning? end def namespace? - namespace && namespace.to_s =~ /^\S/ && namespace != '/' + namespace&.match?(/^\S/) && namespace != '/' end def path? - raw_path && raw_path.to_s =~ /^\S/ && raw_path != '/' + raw_path&.match?(/^\S/) && raw_path != '/' end def suffix diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 66333a5ab..1e621a8d3 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -31,7 +31,7 @@ def method_missing(method_id, *arguments) end def respond_to_missing?(method_id, _) - ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s) + ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s) end %i[ @@ -67,7 +67,6 @@ def initialize(method, pattern, **options) method_s = method.to_s method_upcase = Grape::Http::Headers.find_supported_method(method_s) || method_s.upcase - @suffix = options[:suffix] @options = options.merge(method: method_upcase) @pattern = Pattern.new(pattern, **options) @translator = AttributeTranslator.new(**options, request_method: method_upcase) diff --git a/lib/grape/validations/types/json.rb b/lib/grape/validations/types/json.rb index d401cdc9e..472f73a17 100644 --- a/lib/grape/validations/types/json.rb +++ b/lib/grape/validations/types/json.rb @@ -20,7 +20,7 @@ def call(input) return input if coerced?(input) # Allow nulls and blank strings - return if input.nil? || input =~ /^\s*$/ + return if input.nil? || input.match?(/^\s*$/) JSON.parse(input, symbolize_names: true) end diff --git a/lib/grape/validations/validators/regexp.rb b/lib/grape/validations/validators/regexp.rb index ea220b81c..23f6a29ad 100644 --- a/lib/grape/validations/validators/regexp.rb +++ b/lib/grape/validations/validators/regexp.rb @@ -5,7 +5,7 @@ module Validations class RegexpValidator < Base def validate_param!(attr_name, params) return unless params.respond_to?(:key?) && params.key?(attr_name) - return if Array.wrap(params[attr_name]).all? { |param| param.nil? || (param.to_s =~ (options_key?(:value) ? @option[:value] : @option)) } + return if Array.wrap(params[attr_name]).all? { |param| param.nil? || param.to_s.match?((options_key?(:value) ? @option[:value] : @option)) } raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:regexp)) end end diff --git a/spec/grape/path_spec.rb b/spec/grape/path_spec.rb index e5a2f1c7e..e43e1df4a 100644 --- a/spec/grape/path_spec.rb +++ b/spec/grape/path_spec.rb @@ -87,12 +87,12 @@ module Grape describe '#namespace?' do it 'is false when the namespace is nil' do path = Path.new(anything, nil, anything) - expect(path.namespace?).to be nil + expect(path.namespace?).to be_falsey end it 'is false when the namespace starts with whitespace' do path = Path.new(anything, ' /foo', anything) - expect(path.namespace?).to be nil + expect(path.namespace?).to be_falsey end it 'is false when the namespace is the root path' do @@ -109,12 +109,12 @@ module Grape describe '#path?' do it 'is false when the path is nil' do path = Path.new(nil, anything, anything) - expect(path.path?).to be nil + expect(path.path?).to be_falsey end it 'is false when the path starts with whitespace' do path = Path.new(' /foo', anything, anything) - expect(path.path?).to be nil + expect(path.path?).to be_falsey end it 'is false when the path is the root path' do From 1930c18bb62ec62c25f20b5c0ddc7411b5e99927 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 16 Mar 2020 14:46:51 +0100 Subject: [PATCH 193/290] Add SafeOperator before match --- lib/grape/middleware/versioner/parse_media_type_patch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/grape/middleware/versioner/parse_media_type_patch.rb b/lib/grape/middleware/versioner/parse_media_type_patch.rb index ca7e0671c..7098b32c0 100644 --- a/lib/grape/middleware/versioner/parse_media_type_patch.rb +++ b/lib/grape/middleware/versioner/parse_media_type_patch.rb @@ -8,7 +8,7 @@ class << self # Corrected version of https://github.com/mjackson/rack-accept/blob/master/lib/rack/accept/header.rb#L40-L44 def parse_media_type(media_type) # see http://tools.ietf.org/html/rfc6838#section-4.2 for allowed characters in media type names - m = media_type.match(ALLOWED_CHARACTERS) + m = media_type&.match(ALLOWED_CHARACTERS) m ? [m[1], m[2], m[3] || ''] : [] end end From 5fd457c73175707e64a7ec07847102c56dd224cb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 16 Mar 2020 13:55:00 -0700 Subject: [PATCH 194/290] Add upgrade notice for Array[String] If Array[String] is used without a coerce_with block, existing APIs may quietly fail without explanation. This change to the upgrade documentation makes that clear. Closes https://github.com/ruby-grape/grape/issues/2013 --- UPGRADING.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index ff65254e6..681d9bee6 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -42,6 +42,26 @@ end Custom types which don't depend on Virtus don't require any changes. +#### Ensure that Array[String] types have explicit coercions + +Unlike Virtus, dry-types does not perform any implict coercions. If you +have any uses of `Array[String]`, be sure they use a `coerce_with` +block. For example: + +```ruby +requires :values, type: Array[String] +``` + +It's quite common to pass a comma-separated list, such as `tag1,tag2` as +`values`. Previously Virtus would implicitly coerce this to +`Array(values)` so that `["tag1,tag2"]` would pass the type checks, but +with `dry-types` the values are no longer coerced for you. To fix this, +you might do: + +```ruby +requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) } +``` + For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920). ### Upgrading to >= 1.2.4 From 2a5f6c57c514cf4377c10b8c2b38d11941e183f9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 16 Mar 2020 17:52:48 -0700 Subject: [PATCH 195/290] Add upgrade notice on upgrading all Array types Previously Vitrus implicitly coerced strings to integers when Array[Integer] types were used. With dry-types, this is no longer done, and APIs that expected this behavior will fail with an invalid type check. Expand the upgrade docs to include all Array types and an example for Array[Integer]. --- UPGRADING.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 681d9bee6..57a151247 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -42,11 +42,11 @@ end Custom types which don't depend on Virtus don't require any changes. -#### Ensure that Array[String] types have explicit coercions +#### Ensure that Array types have explicit coercions Unlike Virtus, dry-types does not perform any implict coercions. If you -have any uses of `Array[String]`, be sure they use a `coerce_with` -block. For example: +have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they +use a `coerce_with` block. For example: ```ruby requires :values, type: Array[String] @@ -62,6 +62,12 @@ you might do: requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) } ``` +Likewise, for `Array[Integer]`, you might do: + +```ruby +requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(',').map(&:strip).map(&:to_i) } +``` + For more information see [#1920](https://github.com/ruby-grape/grape/pull/1920). ### Upgrading to >= 1.2.4 From 0e05af76f82ae1be5eaf0e07cdfd6f93c682438d Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Tue, 17 Mar 2020 14:18:15 +0100 Subject: [PATCH 196/290] Concat and Uniq only when needed (#2020) --- CHANGELOG.md | 1 + lib/grape/api/instance.rb | 10 +++++++--- lib/grape/http/headers.rb | 1 + lib/grape/util/base_inheritable.rb | 15 +++++++++------ 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1203fafc3..0d95a5cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Features * Your contribution here. +* [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx). * [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx). * [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx). * [#2011](https://github.com/ruby-grape/grape/pull/2011): Reduce total retained regexes - [@ericproulx](https://github.com/ericproulx). diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index cc0793231..be86c6edc 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -243,9 +243,13 @@ def add_head_not_allowed_methods_and_options_methods # Generate a route that returns an HTTP 405 response for a user defined # path on methods not specified def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) - not_allowed_methods = %w[GET PUT POST DELETE PATCH HEAD] - allowed_methods - not_allowed_methods << Grape::Http::Headers::OPTIONS if self.class.namespace_inheritable(:do_not_route_options) - + supported_methods = + if self.class.namespace_inheritable(:do_not_route_options) + Grape::Http::Headers::SUPPORTED_METHODS + else + Grape::Http::Headers::SUPPORTED_METHODS_WITHOUT_OPTIONS + end + not_allowed_methods = supported_methods - allowed_methods return if not_allowed_methods.empty? @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) diff --git a/lib/grape/http/headers.rb b/lib/grape/http/headers.rb index 4f45044cc..564d97ff8 100644 --- a/lib/grape/http/headers.rb +++ b/lib/grape/http/headers.rb @@ -21,6 +21,7 @@ module Headers OPTIONS = 'OPTIONS' SUPPORTED_METHODS = [GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS].freeze + SUPPORTED_METHODS_WITHOUT_OPTIONS = Grape::Util::LazyObject.new { [GET, POST, PUT, PATCH, DELETE, HEAD].freeze } HTTP_ACCEPT_VERSION = 'HTTP_ACCEPT_VERSION' X_CASCADE = 'X-Cascade' diff --git a/lib/grape/util/base_inheritable.rb b/lib/grape/util/base_inheritable.rb index f1f3378f1..d5c86beed 100644 --- a/lib/grape/util/base_inheritable.rb +++ b/lib/grape/util/base_inheritable.rb @@ -5,8 +5,7 @@ module Util # Base for classes which need to operate with own values kept # in the hash and inherited values kept in a Hash-like object. class BaseInheritable - attr_accessor :inherited_values - attr_accessor :new_values + attr_accessor :inherited_values, :new_values # @param inherited_values [Object] An object implementing an interface # of the Hash class. @@ -26,10 +25,14 @@ def initialize_copy(other) end def keys - combined = inherited_values.keys - combined.concat(new_values.keys) - combined.uniq! - combined + if new_values.any? + combined = inherited_values.keys + combined.concat(new_values.keys) + combined.uniq! + combined + else + inherited_values.keys + end end def key?(name) From 633d7cf529d5aed8a27e2e59364de1e8ca0737d0 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 16 Mar 2020 22:20:09 -0700 Subject: [PATCH 197/290] Avoid coercing parameter with multiple types to an empty Array If an optional parameter has multiple types (e.g. [File, String]) and a nil parameter is passed, Grape would previously coerce the value to an empty Array. This can break certain APIs that treat nil values differently from an empty Array. To fix this, we refactor the nil handling into a `coerce_nil` method and remove the special case check for handling an Array of types. Closes #2018 --- CHANGELOG.md | 1 + lib/grape/validations/validators/coerce.rb | 20 ++++++------- .../validations/validators/coerce_spec.rb | 30 +++++++++++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1203fafc3..26d901d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Fixes * Your contribution here. +* [#2019](https://github.com/ruby-grape/grape/pull/2018): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu). ### 1.3.1 (2020/03/11) diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 561ec1bcf..99317632f 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -65,21 +65,21 @@ def valid_type?(val) end def coerce_value(val) - # define default values for structures, the dry-types lib which is used - # for coercion doesn't accept nil as a value, so it would fail - if val.nil? - return [] if type == Array || type.is_a?(Array) - return Set.new if type == Set - return {} if type == Hash - end - - converter.call(val) - + val.nil? ? coerce_nil(val) : converter.call(val) # Some custom types might fail, so it should be treated as an invalid value rescue StandardError Types::InvalidValue.new end + def coerce_nil(val) + # define default values for structures, the dry-types lib which is used + # for coercion doesn't accept nil as a value, so it would fail + return [] if type == Array + return Set.new if type == Set + return {} if type == Hash + val + end + # Type to which the parameter will be coerced. # # @return [Class] diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index dbf57c9d5..2cfaceea8 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -180,6 +180,23 @@ def self.parsed?(value) expect(last_response.body).to eq(integer_class_name) end + it 'String' do + subject.params do + requires :string, coerce: String + end + subject.get '/string' do + params[:string].class + end + + get '/string', string: 45 + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('String') + + get '/string', string: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + it 'is a custom type' do subject.params do requires :uri, coerce: SecureURIOnly @@ -647,6 +664,19 @@ def self.parsed?(value) expect(last_response.body).to eq('String') end + it 'respects nil values' do + subject.params do + optional :a, types: [File, String] + end + subject.get '/' do + params[:a].class.to_s + end + + get '/', a: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + it 'fails when no coercion is possible' do subject.params do requires :a, types: [Boolean, Integer] From cd03bcf1054a2cba2f8c6a31ca2f1563409f728b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 18 Mar 2020 00:09:29 -0700 Subject: [PATCH 198/290] Fix pull request reference in CHANGELOG.md (#2023) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc917c572..5dab39049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ #### Fixes * Your contribution here. -* [#2019](https://github.com/ruby-grape/grape/pull/2018): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu). +* [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu). ### 1.3.1 (2020/03/11) From c8fd21b86e8e7ac6b92f6632e49db3d2c0dbea24 Mon Sep 17 00:00:00 2001 From: kdoya Date: Fri, 20 Mar 2020 12:26:39 +0900 Subject: [PATCH 199/290] Fix Decimal type category (#2025) --- CHANGELOG.md | 1 + .../validations/types/primitive_coercer.rb | 4 +-- .../validations/validators/coerce_spec.rb | 30 +++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dab39049..4f37b7ea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Fixes * Your contribution here. +* [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya). * [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu). ### 1.3.1 (2020/03/11) diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index 4205b72d0..334bf9049 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -11,10 +11,10 @@ module Types class PrimitiveCoercer < DryTypeCoercer MAPPING = { Grape::API::Boolean => DryTypes::Params::Bool, + BigDecimal => DryTypes::Params::Decimal, # unfortunately, a +Params+ scope doesn't contain String - String => DryTypes::Coercible::String, - BigDecimal => DryTypes::Coercible::Decimal + String => DryTypes::Coercible::String }.freeze STRICT_MAPPING = { diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 2cfaceea8..ba6cd8672 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -154,6 +154,36 @@ def self.parsed?(value) end context 'coerces' do + context 'json' do + let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } } + + it 'BigDecimal' do + subject.params do + requires :bigdecimal, type: BigDecimal + end + subject.post '/bigdecimal' do + params[:bigdecimal] + end + + post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers + expect(last_response.status).to eq(201) + expect(last_response.body).to eq('45.1') + end + + it 'Boolean' do + subject.params do + requires :boolean, type: Boolean + end + subject.post '/boolean' do + params[:boolean] + end + + post '/boolean', { boolean: 'true' }.to_json, headers + expect(last_response.status).to eq(201) + expect(last_response.body).to eq('true') + end + end + it 'BigDecimal' do subject.params do requires :bigdecimal, coerce: BigDecimal From 0219c751e10f7411055aaf340adf7e8eaff5cdcf Mon Sep 17 00:00:00 2001 From: Mikhail Doronin Date: Wed, 25 Mar 2020 07:25:51 +0100 Subject: [PATCH 200/290] Fix a regression in coerce_with when coercion returns nil (#2026) Previously nil was handled as valid value for coercion for integer parameters. With refactoring introduced in 1.3.0 integer parameter coerced with a custom method can't coerce to nil any more. This change reverts back to previous behavior where coercion to nil was allowed. --- CHANGELOG.md | 1 + .../validations/types/custom_type_coercer.rb | 2 +- .../validations/validators/coerce_spec.rb | 40 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f37b7ea6..22cdf8f60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Fixes * Your contribution here. +* [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro). * [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya). * [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu). diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index 70ac7b20a..f6a4e0cf9 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -60,7 +60,7 @@ def call(val) end def coerced?(val) - @type_check.call val + val.nil? || @type_check.call(val) end private diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index ba6cd8672..2badb4c4c 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -525,6 +525,46 @@ def self.parsed?(value) expect(last_response.body).to eq('3') end + context 'Integer type and coerce_with potentially returning nil' do + before do + subject.params do + requires :int, type: Integer, coerce_with: (lambda do |val| + if val == '0' + nil + elsif val.match?(/^-?\d+$/) + val.to_i + else + val + end + end) + end + subject.get '/' do + params[:int].class.to_s + end + end + + it 'accepts value that coerces to nil' do + get '/', int: '0' + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + + it 'coerces to Integer' do + get '/', int: '1' + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('Integer') + end + + it 'returns invalid value if coercion returns a wrong type' do + get '/', int: 'lol' + + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('int is invalid') + end + end + it 'must be supplied with :type or :coerce' do expect do subject.params do From ee1f29f071c6a0a8db24cafbd73a5785f2214cb1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 27 Mar 2020 16:38:05 -0700 Subject: [PATCH 201/290] Improve UPGRADING.md instructions for custom types * Provide an example for taking an existing type and porting it to the dry-types. This would have saved me some time. * Removed examples of using `Virtus.model`. It seems confusing to support dry-types and Virtus at the same time. Closes #2012 --- UPGRADING.md | 53 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 57a151247..0cabe4f2b 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -9,38 +9,57 @@ After adding dry-types, Ruby 2.4 or newer is required. #### Coercion -[Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus, explicitly add it to your `Gemfile`. Also, if Virtus is used for defining custom types +[Virtus](https://github.com/solnic/virtus) has been replaced by +[dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter +coercion. If your project depends on Virtus outside of Grape, explicitly +add it to your `Gemfile`. + +Here's an example of how to migrate a custom type from Virtus to dry-types: ```ruby -class User - include Virtus.model +# Legacy Grape parser +class SecureUriType < Virtus::Attribute + def coerce(input) + URI.parse value + end - attribute :id, Integer - attribute :name, String + def value_coerced?(input) + value.is_a? String + end end -# somewhere in your API params do - requires :user, type: User + requires :secure_uri, type: SecureUri end ``` -Add a class-level `parse` method to the model: +To use dry-types, we need to: -```ruby -class User - include Virtus.model +1. Remove the inheritance of `Virtus::Attribute` +1. Rename `coerce` to `self.parse` +1. Rename `value_coerced?` to `self.parsed?` - attribute :id, Integer - attribute :name, String +The custom type must have a class-level `parse` method to the model. A +class-level `parsed?` is needed if the parsed type differs from the +defined type. In the example below, since `SecureUri` is not the same +as `URI::HTTPS`, `self.parsed?` is needed: - def self.parse(attrs) - new(attrs) +```ruby +# New dry-types parser +class SecureUri + def self.parse(value) + URI.parse value + end + + def self.parsed?(value) + value.is_a? URI::HTTPS end end -``` -Custom types which don't depend on Virtus don't require any changes. +params do + requires :secure_uri, type: SecureUri +end +``` #### Ensure that Array types have explicit coercions From f3073e6123a55e4ec9faa268b741d28502d2dcbc Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 29 Mar 2020 12:56:40 +0300 Subject: [PATCH 202/290] special types provided by Grape are custom ones Users trys to use types provided by Grape as an example how to write custom types. So, it makes sense to treat them as custom. Besides that, it fixes #1986 where a collection of a custom type wasn't coerced properly. --- CHANGELOG.md | 1 + lib/grape/validations/types.rb | 11 +-- lib/grape/validations/types/build_coercer.rb | 7 +- lib/grape/validations/types/file.rb | 28 +++---- lib/grape/validations/types/json.rb | 76 ++++++++++--------- spec/grape/validations/types_spec.rb | 2 +- .../validations/validators/coerce_spec.rb | 76 ++++++++++++------- 7 files changed, 114 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22cdf8f60..84860df4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Fixes * Your contribution here. +* [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk). * [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro). * [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya). * [#2019](https://github.com/ruby-grape/grape/pull/2019): Avoid coercing parameter with multiple types to an empty Array - [@stanhu](https://github.com/stanhu). diff --git a/lib/grape/validations/types.rb b/lib/grape/validations/types.rb index ee60949d8..a682286a5 100644 --- a/lib/grape/validations/types.rb +++ b/lib/grape/validations/types.rb @@ -42,7 +42,6 @@ class InvalidValue; end Grape::API::Boolean, String, Symbol, - Rack::Multipart::UploadedFile, TrueClass, FalseClass ].freeze @@ -54,8 +53,7 @@ class InvalidValue; end Set ].freeze - # Types for which Grape provides special coercion - # and type-checking logic. + # Special custom types provided by Grape. SPECIAL = { JSON => Json, Array[JSON] => JsonArray, @@ -130,7 +128,6 @@ def self.custom?(type) !primitive?(type) && !structure?(type) && !multiple?(type) && - !special?(type) && type.respond_to?(:parse) && type.method(:parse).arity == 1 end @@ -143,7 +140,11 @@ def self.custom?(type) def self.collection_of_custom?(type) (type.is_a?(Array) || type.is_a?(Set)) && type.length == 1 && - custom?(type.first) + (custom?(type.first) || special?(type.first)) + end + + def self.map_special(type) + SPECIAL.fetch(type, type) end end end diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index 90b1dbf01..b867485c1 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -42,6 +42,9 @@ def self.build_coercer(type, method: nil, strict: false) end def self.create_coercer_instance(type, method, strict) + # Maps a custom type provided by Grape, it doesn't map types wrapped by collections!!! + type = Types.map_special(type) + # Use a special coercer for multiply-typed parameters. if Types.multiple?(type) MultipleTypeCoercer.new(type, method) @@ -55,10 +58,8 @@ def self.create_coercer_instance(type, method, strict) # method is supplied. elsif Types.collection_of_custom?(type) Types::CustomTypeCollectionCoercer.new( - type.first, type.is_a?(Set) + Types.map_special(type.first), type.is_a?(Set) ) - elsif Types.special?(type) - Types::SPECIAL[type].new elsif type.is_a?(Array) ArrayCoercer.new type, strict elsif type.is_a?(Set) diff --git a/lib/grape/validations/types/file.rb b/lib/grape/validations/types/file.rb index 6b79389de..8c2f6d924 100644 --- a/lib/grape/validations/types/file.rb +++ b/lib/grape/validations/types/file.rb @@ -7,21 +7,23 @@ module Types # Actual handling of these objects is provided by +Rack::Request+; # this class is here only to assert that rack's handling has succeeded. class File - def call(input) - return if input.nil? - return InvalidValue.new unless coerced?(input) + class << self + def parse(input) + return if input.nil? + return InvalidValue.new unless parsed?(input) - # Processing of multipart file objects - # is already taken care of by Rack::Request. - # Nothing to do here. - input - end + # Processing of multipart file objects + # is already taken care of by Rack::Request. + # Nothing to do here. + input + end - def coerced?(value) - # Rack::Request creates a Hash with filename, - # content type and an IO object. Do a bit of basic - # duck-typing. - value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile) + def parsed?(value) + # Rack::Request creates a Hash with filename, + # content type and an IO object. Do a bit of basic + # duck-typing. + value.is_a?(::Hash) && value.key?(:tempfile) && value[:tempfile].is_a?(Tempfile) + end end end end diff --git a/lib/grape/validations/types/json.rb b/lib/grape/validations/types/json.rb index 472f73a17..25dded6f0 100644 --- a/lib/grape/validations/types/json.rb +++ b/lib/grape/validations/types/json.rb @@ -12,35 +12,37 @@ module Types # validation system will apply nested validation rules to # all returned objects. class Json - # Coerce the input into a JSON-like data structure. - # - # @param input [String] a JSON-encoded parameter value - # @return [Hash,Array,nil] - def call(input) - return input if coerced?(input) + class << self + # Coerce the input into a JSON-like data structure. + # + # @param input [String] a JSON-encoded parameter value + # @return [Hash,Array,nil] + def parse(input) + return input if parsed?(input) - # Allow nulls and blank strings - return if input.nil? || input.match?(/^\s*$/) - JSON.parse(input, symbolize_names: true) - end + # Allow nulls and blank strings + return if input.nil? || input.match?(/^\s*$/) + JSON.parse(input, symbolize_names: true) + end - # Checks that the input was parsed successfully - # and isn't something odd such as an array of primitives. - # - # @param value [Object] result of {#coerce} - # @return [true,false] - def coerced?(value) - value.is_a?(::Hash) || coerced_collection?(value) - end + # Checks that the input was parsed successfully + # and isn't something odd such as an array of primitives. + # + # @param value [Object] result of {#parse} + # @return [true,false] + def parsed?(value) + value.is_a?(::Hash) || coerced_collection?(value) + end - protected + protected - # Is the value an array of JSON-like objects? - # - # @param value [Object] result of {#coerce} - # @return [true,false] - def coerced_collection?(value) - value.is_a?(::Array) && value.all? { |i| i.is_a? ::Hash } + # Is the value an array of JSON-like objects? + # + # @param value [Object] result of {#parse} + # @return [true,false] + def coerced_collection?(value) + value.is_a?(::Array) && value.all? { |i| i.is_a? ::Hash } + end end end @@ -49,18 +51,20 @@ def coerced_collection?(value) # objects and arrays of objects, but wraps single objects # in an Array. class JsonArray < Json - # See {Json#coerce}. Wraps single objects in an array. - # - # @param input [String] JSON-encoded parameter value - # @return [Array] - def call(input) - json = super - Array.wrap(json) unless json.nil? - end + class << self + # See {Json#parse}. Wraps single objects in an array. + # + # @param input [String] JSON-encoded parameter value + # @return [Array] + def parse(input) + json = super + Array.wrap(json) unless json.nil? + end - # See {Json#coerced_collection?} - def coerced?(value) - coerced_collection? value + # See {Json#coerced_collection?} + def parsed?(value) + coerced_collection? value + end end end end diff --git a/spec/grape/validations/types_spec.rb b/spec/grape/validations/types_spec.rb index 133cba130..1bee89e77 100644 --- a/spec/grape/validations/types_spec.rb +++ b/spec/grape/validations/types_spec.rb @@ -17,7 +17,7 @@ def self.parse; end [ Integer, Float, Numeric, BigDecimal, Grape::API::Boolean, String, Symbol, - Date, DateTime, Time, Rack::Multipart::UploadedFile + Date, DateTime, Time ].each do |type| it "recognizes #{type} as a primitive" do expect(described_class.primitive?(type)).to be_truthy diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 2badb4c4c..e77d83934 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -354,42 +354,60 @@ def self.parsed?(value) expect(last_response.body).to eq('TrueClass') end - it 'Rack::Multipart::UploadedFile' do - subject.params do - requires :file, type: Rack::Multipart::UploadedFile - end - subject.post '/upload' do - params[:file][:filename] - end + context 'File' do + let(:file) { Rack::Test::UploadedFile.new(__FILE__) } + let(:filename) { File.basename(__FILE__).to_s } - post '/upload', file: Rack::Test::UploadedFile.new(__FILE__) - expect(last_response.status).to eq(201) - expect(last_response.body).to eq(File.basename(__FILE__).to_s) + it 'Rack::Multipart::UploadedFile' do + subject.params do + requires :file, type: Rack::Multipart::UploadedFile + end + subject.post '/upload' do + params[:file][:filename] + end - post '/upload', file: 'not a file' - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('file is invalid') - end + post '/upload', file: file + expect(last_response.status).to eq(201) + expect(last_response.body).to eq(filename) - it 'File' do - subject.params do - requires :file, coerce: File - end - subject.post '/upload' do - params[:file][:filename] + post '/upload', file: 'not a file' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('file is invalid') end - post '/upload', file: Rack::Test::UploadedFile.new(__FILE__) - expect(last_response.status).to eq(201) - expect(last_response.body).to eq(File.basename(__FILE__).to_s) + it 'File' do + subject.params do + requires :file, coerce: File + end + subject.post '/upload' do + params[:file][:filename] + end - post '/upload', file: 'not a file' - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('file is invalid') + post '/upload', file: file + expect(last_response.status).to eq(201) + expect(last_response.body).to eq(filename) - post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' } - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('file is invalid') + post '/upload', file: 'not a file' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('file is invalid') + + post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' } + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('file is invalid') + end + + it 'collection' do + subject.params do + requires :files, type: Array[File] + end + subject.post '/upload' do + params[:files].first[:filename] + end + + post '/upload', files: [file] + expect(last_response.status).to eq(201) + expect(last_response.body).to eq(filename) + end end it 'Nests integers' do From 4a52bead3bc114c47dd39f12039b2d11ded4bfb3 Mon Sep 17 00:00:00 2001 From: Tim Connor Date: Wed, 1 Apr 2020 08:45:09 +1300 Subject: [PATCH 203/290] ensure floats are correctly coerced to bigdecimal --- CHANGELOG.md | 1 + lib/grape/validations/validators/coerce.rb | 4 +++- spec/grape/validations/validators/coerce_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84860df4b..e2b4dce9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ #### Fixes * Your contribution here. +* [#2033](https://github.com/ruby-grape/grape/pull/2033): Ensure `Float` params are correctly coerced to `BigDecimal` - [@tlconnor](https://github.com/tlconnor). * [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk). * [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro). * [#2025](https://github.com/ruby-grape/grape/pull/2025): Fix Decimal type category - [@kdoya](https://github.com/kdoya). diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 99317632f..8934b2c49 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -47,7 +47,9 @@ def validate_param!(attr_name, params) # h[:list] = list # h # => # - params[attr_name] = new_value unless params[attr_name] == new_value + return if params[attr_name].class == new_value.class && params[attr_name] == new_value + + params[attr_name] = new_value end private diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index e77d83934..f0feff176 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -162,12 +162,12 @@ def self.parsed?(value) requires :bigdecimal, type: BigDecimal end subject.post '/bigdecimal' do - params[:bigdecimal] + "#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}" end post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers expect(last_response.status).to eq(201) - expect(last_response.body).to eq('45.1') + expect(last_response.body).to eq('BigDecimal 45.1') end it 'Boolean' do From 1b91336554ffa5e82478ac4dc7c97e6f82f17a43 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 11 Apr 2020 11:42:15 +0300 Subject: [PATCH 204/290] remove a code example for comparing passwords in Basic authentication --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 004c48b2c..6f0ce47fb 100644 --- a/README.md +++ b/README.md @@ -3189,14 +3189,13 @@ applies to the current namespace and any children, but not parents. ```ruby http_basic do |username, password| # verify user's password here - { 'test' => 'password1' }[username] == password + # IMPORTANT: make sure you use a comparison method which isn't prone to a timing attack end ``` ```ruby http_digest({ realm: 'Test Api', opaque: 'app secret' }) do |username| # lookup the user's password here - { 'user1' => 'password1' }[username] end ``` From 801d17afa41ab12bb1cbd3de7476628eb9726e88 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 12 Apr 2020 10:48:46 +0300 Subject: [PATCH 205/290] Preparing for release, 1.3.2 --- CHANGELOG.md | 4 +--- README.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b4dce9b..f8958b18c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,6 @@ -### 1.3.2 (Next) +### 1.3.2 (2020/04/12) #### Features -* Your contribution here. * [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx). * [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx). * [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx). @@ -9,7 +8,6 @@ #### Fixes -* Your contribution here. * [#2033](https://github.com/ruby-grape/grape/pull/2033): Ensure `Float` params are correctly coerced to `BigDecimal` - [@tlconnor](https://github.com/tlconnor). * [#2031](https://github.com/ruby-grape/grape/pull/2031): Fix a regression with an array of a custom type - [@dnesteryuk](https://github.com/dnesteryuk). * [#2026](https://github.com/ruby-grape/grape/pull/2026): Fix a regression in `coerce_with` when coercion returns `nil` - [@misdoro](https://github.com/misdoro). diff --git a/README.md b/README.md index 6f0ce47fb..3c9af6cab 100644 --- a/README.md +++ b/README.md @@ -154,9 +154,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.3.2**. -Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.3.1](https://github.com/ruby-grape/grape/blob/v1.3.1/README.md). +You're reading the documentation for the stable release of Grape, 1.3.2. ## Project Resources From 0072f010d89feff76607cca199f4b84516410725 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 12 Apr 2020 10:52:19 +0300 Subject: [PATCH 206/290] Preparing for next development iteration, 1.3.3 --- CHANGELOG.md | 10 ++++++++++ README.md | 4 +++- lib/grape/version.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8958b18c..5ab0bb87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.3.3 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.3.2 (2020/04/12) #### Features diff --git a/README.md b/README.md index 3c9af6cab..0c0a7564b 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, 1.3.2. +You're reading the documentation for the next release of Grape, which should be **1.3.3**. +Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.3.2](https://github.com/ruby-grape/grape/blob/v1.3.2/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 260d092ab..db729705f 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.3.2' + VERSION = '1.3.3' end From 871fc1913e0f84f5a667e9272145f61c2b69b9da Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Thu, 16 Apr 2020 13:24:58 +0200 Subject: [PATCH 207/290] Update travis ruby versions Add CHANGELOG.md --- .travis.yml | 30 +++++++++++++++--------------- CHANGELOG.md | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 66e723b47..d10934f3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,42 +8,42 @@ gemfile: matrix: include: - - rvm: 2.7.0 + - rvm: 2.7.1 script: - bundle exec danger - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: Gemfile - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/rack1.gemfile - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/rack2.gemfile - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/multi_json.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_json - - rvm: 2.7.0 + - rvm: 2.7.1 gemfile: gemfiles/multi_xml.gemfile script: - bundle exec rake - bundle exec rspec spec/integration/multi_xml - - rvm: 2.6.5 + - rvm: 2.6.6 gemfile: Gemfile - - rvm: 2.6.5 + - rvm: 2.6.6 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.5.7 + - rvm: 2.5.8 gemfile: Gemfile - - rvm: 2.5.7 + - rvm: 2.5.8 gemfile: gemfiles/rails_5.gemfile - - rvm: 2.4.9 + - rvm: 2.4.10 gemfile: Gemfile - - rvm: 2.4.9 + - rvm: 2.4.10 gemfile: gemfiles/rails_5.gemfile - rvm: ruby-head - rvm: jruby-head diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ab0bb87a..2ef8a49b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx). #### Fixes From e0dfb9cb4ec0aac5d3ec37fbae149676cf3ded11 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Thu, 16 Apr 2020 18:56:26 +0200 Subject: [PATCH 208/290] Add rails-6 (#2039) --- .travis.yml | 6 ++++++ Appraisals | 6 +++++- CHANGELOG.md | 1 + gemfiles/rails_5.gemfile | 2 +- gemfiles/rails_6.gemfile | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 gemfiles/rails_6.gemfile diff --git a/.travis.yml b/.travis.yml index d10934f3b..82bd2e647 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ matrix: gemfile: gemfiles/rails_edge.gemfile - rvm: 2.7.1 gemfile: gemfiles/rails_5.gemfile + - rvm: 2.7.1 + gemfile: gemfiles/rails_6.gemfile - rvm: 2.7.1 gemfile: gemfiles/multi_json.gemfile script: @@ -37,10 +39,14 @@ matrix: gemfile: Gemfile - rvm: 2.6.6 gemfile: gemfiles/rails_5.gemfile + - rvm: 2.6.6 + gemfile: gemfiles/rails_6.gemfile - rvm: 2.5.8 gemfile: Gemfile - rvm: 2.5.8 gemfile: gemfiles/rails_5.gemfile + - rvm: 2.5.8 + gemfile: gemfiles/rails_6.gemfile - rvm: 2.4.10 gemfile: Gemfile - rvm: 2.4.10 diff --git a/Appraisals b/Appraisals index e9c0526e0..7e17deba5 100644 --- a/Appraisals +++ b/Appraisals @@ -1,7 +1,11 @@ # frozen_string_literal: true appraise 'rails-5' do - gem 'rails', '5.2.1' + gem 'rails', '~> 5.2' +end + +appraise 'rails-6' do + gem 'rails', '~> 6.0' end appraise 'rails-edge' do diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ef8a49b1..4b02b3431 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx). * [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx). #### Fixes diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 493f94e3b..5de94c557 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -4,7 +4,7 @@ source 'https://rubygems.org' -gem 'rails', '5.2.1' +gem 'rails', '~> 5.2' group :development, :test do gem 'bundler' diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile new file mode 100644 index 000000000..b08046c16 --- /dev/null +++ b/gemfiles/rails_6.gemfile @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source 'https://rubygems.org' + +gem 'rails', '~> 6.0' + +group :development, :test do + gem 'bundler' + gem 'hashie' + gem 'rake' + gem 'rubocop', '0.80.1' + gem 'rubocop-performance', require: false +end + +group :development do + gem 'appraisal' + gem 'benchmark-ips' + gem 'guard' + gem 'guard-rspec' + gem 'guard-rubocop' +end + +group :test do + gem 'cookiejar' + gem 'coveralls_reborn' + gem 'danger-toc', '~> 0.1.2' + gem 'grape-entity', '~> 0.6' + gem 'maruku' + gem 'mime-types' + gem 'rack-jsonp', require: 'rack/jsonp' + gem 'rack-test', '~> 1.1.0' + gem 'rspec', '~> 3.0' + gem 'ruby-grape-danger', '~> 0.1.0', require: false +end + +gemspec path: '../' From 3551c67032340f06d00b84de58815e86718e4e43 Mon Sep 17 00:00:00 2001 From: kadotami Date: Mon, 27 Apr 2020 14:13:45 +0900 Subject: [PATCH 209/290] Modify declare for nested array and hash #2042 --- CHANGELOG.md | 1 + lib/grape/dsl/inside_route.rb | 51 +++++++++++++++++++++++++---------- spec/grape/endpoint_spec.rb | 23 ++++++++++++---- 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b02b3431..61a04012e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx). #### Fixes +* [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). * Your contribution here. diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 3fa4d313b..7739db845 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -28,36 +28,38 @@ def self.post_filter_methods(type) # Methods which should not be available in filters until the before filter # has completed module PostBeforeFilter - def declared(passed_params, options = {}, declared_params = nil) + def declared(passed_params, options = {}, declared_params = nil, params_nested_path = []) options = options.reverse_merge(include_missing: true, include_parent_namespaces: true) declared_params ||= optioned_declared_params(**options) if passed_params.is_a?(Array) - declared_array(passed_params, options, declared_params) + declared_array(passed_params, options, declared_params, params_nested_path) else - declared_hash(passed_params, options, declared_params) + declared_hash(passed_params, options, declared_params, params_nested_path) end end private - def declared_array(passed_params, options, declared_params) + def declared_array(passed_params, options, declared_params, params_nested_path) passed_params.map do |passed_param| - declared(passed_param || {}, options, declared_params) + declared(passed_param || {}, options, declared_params, params_nested_path) end end - def declared_hash(passed_params, options, declared_params) + def declared_hash(passed_params, options, declared_params, params_nested_path) declared_params.each_with_object(passed_params.class.new) do |declared_param, memo| if declared_param.is_a?(Hash) declared_param.each_pair do |declared_parent_param, declared_children_params| + params_nested_path_dup = params_nested_path.dup + params_nested_path_dup << declared_parent_param.to_s next unless options[:include_missing] || passed_params.key?(declared_parent_param) passed_children_params = passed_params[declared_parent_param] || passed_params.class.new memo_key = optioned_param_key(declared_parent_param, options) - memo[memo_key] = handle_passed_param(declared_parent_param, passed_children_params) do - declared(passed_children_params, options, declared_children_params) + memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do + declared(passed_children_params, options, declared_children_params, params_nested_path_dup) end end else @@ -77,19 +79,34 @@ def declared_hash(passed_params, options, declared_params) end end - def handle_passed_param(declared_param, passed_children_params, &_block) - should_be_empty_array?(declared_param, passed_children_params) ? [] : yield + def handle_passed_param(passed_children_params, params_nested_path, &_block) + if should_be_empty_hash?(passed_children_params, params_nested_path) + {} + elsif should_be_empty_array?(passed_children_params, params_nested_path) + [] + else + yield + end end - def should_be_empty_array?(declared_param, passed_children_params) - declared_param_is_array?(declared_param) && passed_children_params.empty? + def should_be_empty_array?(passed_children_params, params_nested_path) + passed_children_params.empty? && declared_param_is_array?(params_nested_path) end - def declared_param_is_array?(declared_param) - key = declared_param.to_s + def declared_param_is_array?(params_nested_path) + key = route_options_params_key(params_nested_path) route_options_params[key] && route_options_params[key][:type] == 'Array' end + def should_be_empty_hash?(passed_children_params, params_nested_path) + passed_children_params.empty? && declared_param_is_hash?(params_nested_path) + end + + def declared_param_is_hash?(params_nested_path) + key = route_options_params_key(params_nested_path) + route_options_params[key] && route_options_params[key][:type] == 'Hash' + end + def route_options_params options[:route_options][:params] || {} end @@ -98,6 +115,12 @@ def optioned_param_key(declared_param, options) options[:stringify] ? declared_param.to_s : declared_param.to_sym end + def route_options_params_key(params_nested_path) + key = params_nested_path[0] + key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1 + key + end + def optioned_declared_params(**options) declared_params = if options[:include_parent_namespaces] # Declared params including parent namespaces diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 37aea84bc..a45abe36d 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -296,9 +296,12 @@ def app optional :seventh end end + optional :nested_arr, type: Array do + optional :eighth + end end - optional :nested_arr, type: Array do - optional :eighth + optional :arr, type: Array do + optional :nineth end end end @@ -390,7 +393,7 @@ def app get '/declared?first=present&nested[fourth]=1' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 3 + expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 4 end it 'builds nested params when given array' do @@ -421,7 +424,7 @@ def app get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']).to be_a(Hash) + expect(JSON.parse(last_response.body)['nested']).to eq({}) end it 'to be an array when include_missing is true' do @@ -431,7 +434,17 @@ def app get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested_arr']).to be_a(Array) + expect(JSON.parse(last_response.body)['arr']).to be_a(Array) + end + + it 'to be an array when nested and include_missing is true' do + subject.get '/declared' do + declared(params, include_missing: true) + end + + get '/declared?first=present&nested[fourth]=1' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested']['nested_arr']).to be_a(Array) end it 'to be nil when include_missing is false' do From aa1e1fbce7e4c3996ebf69852ff47c63f624b965 Mon Sep 17 00:00:00 2001 From: dblock Date: Sat, 2 May 2020 11:37:07 -0400 Subject: [PATCH 210/290] Added SECURITY.md. [ci-skip] --- SECURITY.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..6a8cc7d6a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,10 @@ +# Security Policy + +## Supported Versions + +Version 1.2.0 or newer is currently supported. + +## Reporting a Vulnerability + +Tidelift acts as the security contact for this open-source project. To make a report, please email the security team at security@tidelift.com. See [tidelift.com/security](https://tidelift.com/security) for details and more options. + From 30b3dcacb2fc09651511a3088d4a7d587d44adef Mon Sep 17 00:00:00 2001 From: "Daniel Doubrovkine (dB.) @dblockdotorg" Date: Sat, 2 May 2020 11:42:53 -0400 Subject: [PATCH 211/290] Added FUNDING.yml to link to TideLift. Part of #2034. --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..6768fc5a6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +tidelift: "rubygems/grape" From 873b5d64005ffdd6faf6f4760f7f928da7ceb711 Mon Sep 17 00:00:00 2001 From: dblock Date: Sat, 2 May 2020 11:55:11 -0400 Subject: [PATCH 212/290] Document TideLift Enterprise support. --- CHANGELOG.md | 6 ++++-- LICENSE | 2 +- README.md | 20 +++++++++++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61a04012e..0517bbb63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,20 @@ #### Features -* Your contribution here. +* [#2048](https://github.com/ruby-grape/grape/issues/2034): Grape Enterprise support is now available [via TideLift](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) - [@dblock](https://github.com/dblock). * [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx). * [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx). +* Your contribution here. #### Fixes -* [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). +* [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). * Your contribution here. ### 1.3.2 (2020/04/12) #### Features + * [#2020](https://github.com/ruby-grape/grape/pull/2020): Reduce array allocation - [@ericproulx](https://github.com/ericproulx). * [#2015](https://github.com/ruby-grape/grape/pull/2014): Reduce MatchData allocation - [@ericproulx](https://github.com/ericproulx). * [#2014](https://github.com/ruby-grape/grape/pull/2014): Reduce total allocated arrays - [@ericproulx](https://github.com/ericproulx). diff --git a/LICENSE b/LICENSE index 05d951bc4..e689bdce6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2019 Michael Bleigh, Intridea Inc. and Contributors. +Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index 0c0a7564b..37c762829 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ - [What is Grape?](#what-is-grape) - [Stable Release](#stable-release) - [Project Resources](#project-resources) +- [Grape for Enterprise](#grape-for-enterprise) - [Installation](#installation) - [Basic Usage](#basic-usage) - [Mounting](#mounting) @@ -141,6 +142,7 @@ - [format_response.grape](#format_responsegrape) - [Monitoring Products](#monitoring-products) - [Contributing to Grape](#contributing-to-grape) +- [Security](#security) - [License](#license) - [Copyright](#copyright) @@ -165,6 +167,14 @@ The current stable release is [1.3.2](https://github.com/ruby-grape/grape/blob/v * Need help? Try [Grape Google Group](http://groups.google.com/group/ruby-grape) or [Gitter](https://gitter.im/ruby-grape/grape) * [Follow us on Twitter](https://twitter.com/grapeframework) +## Grape for Enterprise + +Available as part of the Tidelift Subscription. + +The maintainers of Grape are working with Tidelift to deliver commercial support and maintenance. Save time, reduce risk, and improve code health, while paying the maintainers of Grape. Click [here](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) for more details. + +In 2020, we plan to use the money towards gathering Grape contributors for dinner in New York City. + ## Installation Ruby 2.4 or newer is required. @@ -3852,7 +3862,7 @@ Grape integrates with following third-party tools: * **Librato Metrics** - [grape-librato](https://github.com/seanmoon/grape-librato) gem * **[Skylight](https://www.skylight.io/)** - [skylight](https://github.com/skylightio/skylight-ruby) gem, [documentation](https://docs.skylight.io/grape/) * **[AppSignal](https://www.appsignal.com)** - [appsignal-ruby](https://github.com/appsignal/appsignal-ruby) gem, [documentation](http://docs.appsignal.com/getting-started/supported-frameworks.html#grape) -* **[ElasticAPM](https://www.elastic.co/products/apm) - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape) +* **[ElasticAPM](https://www.elastic.co/products/apm)** - [elastic-apm](https://github.com/elastic/apm-agent-ruby) gem, [documentation](https://www.elastic.co/guide/en/apm/agent/ruby/3.x/getting-started-rack.html#getting-started-grape) ## Contributing to Grape @@ -3861,10 +3871,14 @@ features and discuss issues. See [CONTRIBUTING](CONTRIBUTING.md). +## Security + +See [SECURITY](SECURITY.md) for details. + ## License -MIT License. See LICENSE for details. +MIT License. See [LICENSE](LICENSE) for details. ## Copyright -Copyright (c) 2010-2019 Michael Bleigh, Intridea Inc. and Contributors. +Copyright (c) 2010-2020 Michael Bleigh, Intridea Inc. and Contributors. From 67f9417238218dac46957748d5869477f0c9e124 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 3 May 2020 17:31:30 +0200 Subject: [PATCH 213/290] Transform translator public_send to actual methods in attribute_translator.rb Remove Any class in route and replace by pure AttributeTranslator --- CHANGELOG.md | 1 + lib/grape/router.rb | 11 +---------- lib/grape/router/attribute_translator.rb | 25 ++++++++++++++++++++++-- lib/grape/router/route.rb | 20 +------------------ 4 files changed, 26 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0517bbb63..a489278d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [#2048](https://github.com/ruby-grape/grape/issues/2034): Grape Enterprise support is now available [via TideLift](https://tidelift.com/subscription/request-a-demo?utm_source=rubygems-grape&utm_medium=referral&utm_campaign=enterprise) - [@dblock](https://github.com/dblock). * [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx). * [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx). +* [#2050](https://github.com/ruby-grape/grape/pull/2050): Refactor route public_send to AttributeTranslator - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/lib/grape/router.rb b/lib/grape/router.rb index d8428e426..26c7f1a0d 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -7,15 +7,6 @@ module Grape class Router attr_reader :map, :compiled - class Any < AttributeTranslator - attr_reader :pattern, :index - def initialize(pattern, index, **attributes) - @pattern = pattern - @index = index - super(attributes) - end - end - class NormalizePathCache < Grape::Util::Cache def initialize @cache = Hash.new do |h, path| @@ -64,7 +55,7 @@ def append(route) def associate_routes(pattern, **options) @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}") - @neutral_map << Any.new(pattern, @neutral_map.length, **options) + @neutral_map << Grape::Router::AttributeTranslator.new(options.merge(pattern: pattern, index: @neutral_map.length)) end def call(env) diff --git a/lib/grape/router/attribute_translator.rb b/lib/grape/router/attribute_translator.rb index ad2d03855..93d112fcb 100644 --- a/lib/grape/router/attribute_translator.rb +++ b/lib/grape/router/attribute_translator.rb @@ -6,10 +6,31 @@ class Router class AttributeTranslator attr_reader :attributes, :request_method, :requirements + ROUTE_ATTRIBUTES = %i[ + prefix + version + settings + format + description + http_codes + headers + entity + details + requirements + request_method + namespace + ].freeze + + ROUTER_ATTRIBUTES = %i[pattern index].freeze + def initialize(attributes = {}) @attributes = attributes - @request_method = attributes[:request_method] - @requirements = attributes[:requirements] + end + + (ROUTER_ATTRIBUTES + ROUTE_ATTRIBUTES).each do |attr| + define_method attr do + attributes[attr] + end end def to_h diff --git a/lib/grape/router/route.rb b/lib/grape/router/route.rb index 1e621a8d3..fa940c925 100644 --- a/lib/grape/router/route.rb +++ b/lib/grape/router/route.rb @@ -18,6 +18,7 @@ class Route extend Forwardable def_delegators :pattern, :path, :origin + delegate Grape::Router::AttributeTranslator::ROUTE_ATTRIBUTES => :attributes def method_missing(method_id, *arguments) match = ROUTE_ATTRIBUTE_REGEXP.match(method_id.to_s) @@ -34,25 +35,6 @@ def respond_to_missing?(method_id, _) ROUTE_ATTRIBUTE_REGEXP.match?(method_id.to_s) end - %i[ - prefix - version - settings - format - description - http_codes - headers - entity - details - requirements - request_method - namespace - ].each do |method_name| - define_method method_name do - attributes.public_send method_name - end - end - def route_method warn_route_methods(:method, caller(1).shift, :request_method) request_method From 01ca08c9976eb83440915c327d4bf8d957d321d3 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Sun, 3 May 2020 19:32:37 +0200 Subject: [PATCH 214/290] Fix coerce_nil when Array of Type (#2040) --- CHANGELOG.md | 1 + UPGRADING.md | 59 +++++++++ lib/grape/validations/types/array_coercer.rb | 2 + .../validations/types/dry_type_coercer.rb | 2 + .../types/variant_collection_coercer.rb | 2 +- lib/grape/validations/validators/coerce.rb | 11 +- lib/grape/validations/validators/default.rb | 1 - .../validations/validators/coerce_spec.rb | 86 +++++++++++-- .../validations/validators/default_spec.rb | 121 ++++++++++++++++++ .../validations/validators/values_spec.rb | 2 +- spec/grape/validations_spec.rb | 10 +- 11 files changed, 266 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0517bbb63..e7c6ac516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). * Your contribution here. +* [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx). ### 1.3.2 (2020/04/12) diff --git a/UPGRADING.md b/UPGRADING.md index 0cabe4f2b..8cdec92c8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,65 @@ Upgrading Grape =============== +### Upgrading to >= 1.4.0 + +#### Nil values for structures + +Nil values always been a special case when dealing with types especially with the following structures: + - Array + - Hash + - Set + +The behaviour for these structures has change through out the latest releases. For instance: + +```ruby +class Api < Grape::API + params do + require :my_param, type: Array[Integer] + end + + get 'example' do + params[:my_param] + end + get '/example', params: { my_param: nil } + # 1.3.1 = [] + # 1.3.2 = nil +end +``` +For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes. + +If you want to have the same behavior as 1.3.1, apply a `default` validator + +```ruby +class Api < Grape::API + params do + require :my_param, type: Array[Integer], default: [] + end + + get 'example' do + params[:my_param] + end + get '/example', params: { my_param: nil } # => [] +end +``` + +#### Default validator + +Default validator is now applied for `nil` values. + +```ruby +class Api < Grape::API + params do + requires :my_param, type: Integer, default: 0 + end + + get 'example' do + params[:my_param] + end + get '/example', params: { my_param: nil } #=> before: nil, after: 0 +end +``` + ### Upgrading to >= 1.3.0 #### Ruby diff --git a/lib/grape/validations/types/array_coercer.rb b/lib/grape/validations/types/array_coercer.rb index 4f456a741..8dd819e0d 100644 --- a/lib/grape/validations/types/array_coercer.rb +++ b/lib/grape/validations/types/array_coercer.rb @@ -32,6 +32,8 @@ def call(_val) protected def coerce_elements(collection) + return if collection.nil? + collection.each_with_index do |elem, index| return InvalidValue.new if reject?(elem) diff --git a/lib/grape/validations/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb index a1c5fb36f..06d234d55 100644 --- a/lib/grape/validations/types/dry_type_coercer.rb +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -27,6 +27,8 @@ def initialize(type, strict = false) # # @param val [Object] def call(val) + return if val.nil? + @coercer[val] rescue Dry::Types::CoercionError => _e InvalidValue.new diff --git a/lib/grape/validations/types/variant_collection_coercer.rb b/lib/grape/validations/types/variant_collection_coercer.rb index 9495c891c..34982e133 100644 --- a/lib/grape/validations/types/variant_collection_coercer.rb +++ b/lib/grape/validations/types/variant_collection_coercer.rb @@ -33,7 +33,7 @@ def initialize(types, method = nil) # the coerced result, or an instance # of {InvalidValue} if the value could not be coerced. def call(value) - return InvalidValue.new unless value.is_a? Array + return unless value.is_a? Array value = if @method diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 8934b2c49..5b6f960a6 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -67,21 +67,12 @@ def valid_type?(val) end def coerce_value(val) - val.nil? ? coerce_nil(val) : converter.call(val) + converter.call(val) # Some custom types might fail, so it should be treated as an invalid value rescue StandardError Types::InvalidValue.new end - def coerce_nil(val) - # define default values for structures, the dry-types lib which is used - # for coercion doesn't accept nil as a value, so it would fail - return [] if type == Array - return Set.new if type == Set - return {} if type == Hash - val - end - # Type to which the parameter will be coerced. # # @return [Class] diff --git a/lib/grape/validations/validators/default.rb b/lib/grape/validations/validators/default.rb index c6c16529e..ee620db46 100644 --- a/lib/grape/validations/validators/default.rb +++ b/lib/grape/validations/validators/default.rb @@ -9,7 +9,6 @@ def initialize(attrs, options, required, scope, opts = {}) end def validate_param!(attr_name, params) - return if params.key? attr_name params[attr_name] = if @default.is_a? Proc @default.call elsif @default.frozen? || !duplicatable?(@default) diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index f0feff176..e6537ff54 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -424,6 +424,79 @@ def self.parsed?(value) expect(last_response.status).to eq(200) expect(last_response.body).to eq(integer_class_name) end + + context 'nil values' do + context 'primitive types' do + Grape::Validations::Types::PRIMITIVES.each do |type| + it 'respects the nil value' do + subject.params do + requires :param, type: type + end + subject.get '/nil_value' do + params[:param].class + end + + get '/nil_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + end + + context 'structures types' do + Grape::Validations::Types::STRUCTURES.each do |type| + it 'respects the nil value' do + subject.params do + requires :param, type: type + end + subject.get '/nil_value' do + params[:param].class + end + + get '/nil_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + end + + context 'special types' do + Grape::Validations::Types::SPECIAL.each_key do |type| + it 'respects the nil value' do + subject.params do + requires :param, type: type + end + subject.get '/nil_value' do + params[:param].class + end + + get '/nil_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + + context 'variant-member-type collections' do + [ + Array[Integer, String], + [Integer, String, Array[Integer, String]] + ].each do |type| + it 'respects the nil value' do + subject.params do + requires :param, type: type + end + subject.get '/nil_value' do + params[:param].class + end + + get '/nil_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + end + end + end end context 'using coerce_with' do @@ -752,19 +825,6 @@ def self.parsed?(value) expect(last_response.body).to eq('String') end - it 'respects nil values' do - subject.params do - optional :a, types: [File, String] - end - subject.get '/' do - params[:a].class.to_s - end - - get '/', a: nil - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('NilClass') - end - it 'fails when no coercion is possible' do subject.params do requires :a, types: [Boolean, Integer] diff --git a/spec/grape/validations/validators/default_spec.rb b/spec/grape/validations/validators/default_spec.rb index f110fe0e8..10220eb58 100644 --- a/spec/grape/validations/validators/default_spec.rb +++ b/spec/grape/validations/validators/default_spec.rb @@ -298,4 +298,125 @@ def app end end end + + context 'optional with nil as value' do + subject do + Class.new(Grape::API) do + default_format :json + end + end + + def app + subject + end + + context 'primitive types' do + [ + [Integer, 0], + [Integer, 42], + [Float, 0.0], + [Float, 4.2], + [BigDecimal, 0.0], + [BigDecimal, 4.2], + [Numeric, 0], + [Numeric, 42], + [Date, Date.today], + [DateTime, DateTime.now], + [Time, Time.now], + [Time, Time.at(0)], + [Grape::API::Boolean, false], + [String, ''], + [String, 'non-empty-string'], + [Symbol, :symbol], + [TrueClass, true], + [FalseClass, false] + ].each do |type, default| + it 'respects the default value' do + subject.params do + optional :param, type: type, default: default + end + subject.get '/default_value' do + params[:param] + end + + get '/default_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq(default.to_json) + end + end + end + + context 'structures types' do + [ + [Hash, {}], + [Hash, { test: 'non-empty' }], + [Array, []], + [Array, ['non-empty']], + [Array[Integer], []], + [Set, []], + [Set, [1]] + ].each do |type, default| + it 'respects the default value' do + subject.params do + optional :param, type: type, default: default + end + subject.get '/default_value' do + params[:param] + end + + get '/default_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq(default.to_json) + end + end + end + + context 'special types' do + [ + [JSON, ''], + [JSON, { test: 'non-empty-string' }.to_json], + [Array[JSON], []], + [Array[JSON], [{ test: 'non-empty-string' }.to_json]], + [::File, ''], + [::File, { test: 'non-empty-string' }.to_json], + [Rack::Multipart::UploadedFile, ''], + [Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json] + ].each do |type, default| + it 'respects the default value' do + subject.params do + optional :param, type: type, default: default + end + subject.get '/default_value' do + params[:param] + end + + get '/default_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq(default.to_json) + end + end + end + + context 'variant-member-type collections' do + [ + [Array[Integer, String], [0, '']], + [Array[Integer, String], [42, 'non-empty-string']], + [[Integer, String, Array[Integer, String]], [0, '', [0, '']]], + [[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]] + ].each do |type, default| + it 'respects the default value' do + subject.params do + optional :param, type: type, default: default + end + subject.get '/default_value' do + params[:param] + end + + get '/default_value', param: nil + expect(last_response.status).to eq(200) + expect(last_response.body).to eq(default.to_json) + end + end + end + end end diff --git a/spec/grape/validations/validators/values_spec.rb b/spec/grape/validations/validators/values_spec.rb index b975c3feb..491b32834 100644 --- a/spec/grape/validations/validators/values_spec.rb +++ b/spec/grape/validations/validators/values_spec.rb @@ -319,7 +319,7 @@ def app expect(last_response.status).to eq 200 end - it 'allows for an optional param with a list of values' do + it 'accepts for an optional param with a list of values' do put('/optional_with_array_of_string_values', optional: nil) expect(last_response.status).to eq 200 end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index e690ccb7d..7bde6104a 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -574,7 +574,7 @@ def validate_param!(attr_name, params) # NOTE: with body parameters in json or XML or similar this # should actually fail with: children[parents][name] is missing. expect(last_response.status).to eq(400) - expect(last_response.body).to eq('children[1][parents] is missing') + expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty') end it 'errors when a parameter is not present in array within array' do @@ -615,7 +615,7 @@ def validate_param!(attr_name, params) get '/within_array', children: [name: 'Jay'] expect(last_response.status).to eq(400) - expect(last_response.body).to eq('children[0][parents] is missing') + expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty') end it 'errors when param is not an Array' do @@ -763,7 +763,7 @@ def validate_param!(attr_name, params) expect(last_response.status).to eq(200) put_with_json '/within_array', children: [name: 'Jay'] expect(last_response.status).to eq(400) - expect(last_response.body).to eq('children[0][parents] is missing') + expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing') end end @@ -838,7 +838,7 @@ def validate_param!(attr_name, params) it 'does internal validations if the outer group is present' do get '/nested_optional_group', items: [{ key: 'foo' }] expect(last_response.status).to eq(400) - expect(last_response.body).to eq('items[0][required_subitems] is missing') + expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing') get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }] expect(last_response.status).to eq(200) @@ -858,7 +858,7 @@ def validate_param!(attr_name, params) it 'handles validation within arrays' do get '/nested_optional_group', items: [{ key: 'foo' }] expect(last_response.status).to eq(400) - expect(last_response.body).to eq('items[0][required_subitems] is missing') + expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing') get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }] expect(last_response.status).to eq(200) From 23374d64ab36e92c9dd98e1b29dcc554b1912ad1 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 3 May 2020 21:28:58 +0300 Subject: [PATCH 215/290] coerce an empty string to nil in case of the bool type (#2049) --- CHANGELOG.md | 3 ++- lib/grape/validations/types/primitive_coercer.rb | 11 +++++++++-- .../grape/validations/types/primitive_coercer_spec.rb | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f81ad8b..c17ac7432 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,10 @@ #### Fixes +* [#2049](https://github.com/ruby-grape/grape/pull/2049): Coerce an empty string to nil in case of the bool type - [@dnesteryuk](https://github.com/dnesteryuk). * [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). -* Your contribution here. * [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx). +* Your contribution here. ### 1.3.2 (2020/04/12) diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index 334bf9049..b7656e39a 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -36,7 +36,7 @@ def initialize(type, strict = false) def call(val) return InvalidValue.new if reject?(val) - return nil if val.nil? + return nil if val.nil? || treat_as_nil?(val) return '' if val == '' super @@ -46,7 +46,7 @@ def call(val) attr_reader :type - # This method maintaine logic which was defined by Virtus. For example, + # This method maintains logic which was defined by Virtus. For example, # dry-types is ok to convert an array or a hash to a string, it is supported, # but Virtus wouldn't accept it. So, this method only exists to not introduce # breaking changes. @@ -55,6 +55,13 @@ def reject?(val) (val.is_a?(String) && type == Hash) || (val.is_a?(Hash) && type == String) end + + # Dry-Types treats an empty string as invalid. However, Grape considers an empty string as + # absence of a value and coerces it into nil. See a discussion there + # https://github.com/ruby-grape/grape/pull/2045 + def treat_as_nil?(val) + val == '' && type == Grape::API::Boolean + end end end end diff --git a/spec/grape/validations/types/primitive_coercer_spec.rb b/spec/grape/validations/types/primitive_coercer_spec.rb index d215bf332..d0e1b137b 100644 --- a/spec/grape/validations/types/primitive_coercer_spec.rb +++ b/spec/grape/validations/types/primitive_coercer_spec.rb @@ -26,6 +26,10 @@ it 'returns an error when the given value cannot be coerced' do expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue) end + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end end context 'String' do From 18ed28c5c1e8dd99f23d66a97408e421e290c87e Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 17 May 2020 00:38:30 +0300 Subject: [PATCH 216/290] coercing of nested arrays (#2054) --- CHANGELOG.md | 1 + lib/grape/validations/types/array_coercer.rb | 17 ++++++--- lib/grape/validations/types/build_coercer.rb | 6 +--- .../validations/types/dry_type_coercer.rb | 35 ++++++++++++++++++- lib/grape/validations/types/set_coercer.rb | 10 +++--- .../validations/types/array_coercer_spec.rb | 35 +++++++++++++++++++ .../types/primitive_coercer_spec.rb | 2 +- .../validations/types/set_coercer_spec.rb | 34 ++++++++++++++++++ 8 files changed, 124 insertions(+), 16 deletions(-) create mode 100644 spec/grape/validations/types/array_coercer_spec.rb create mode 100644 spec/grape/validations/types/set_coercer_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index c17ac7432..dd6324d7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2049](https://github.com/ruby-grape/grape/pull/2049): Coerce an empty string to nil in case of the bool type - [@dnesteryuk](https://github.com/dnesteryuk). * [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). * [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx). +* [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk). * Your contribution here. ### 1.3.2 (2020/04/12) diff --git a/lib/grape/validations/types/array_coercer.rb b/lib/grape/validations/types/array_coercer.rb index 8dd819e0d..d3aeb2146 100644 --- a/lib/grape/validations/types/array_coercer.rb +++ b/lib/grape/validations/types/array_coercer.rb @@ -6,7 +6,7 @@ module Grape module Validations module Types # Coerces elements in an array. It might be an array of strings or integers or - # anything else. + # an array of arrays of integers. # # It could've been possible to use an +of+ # method (https://dry-rb.org/gems/dry-types/1.2/array-with-member/) @@ -14,16 +14,17 @@ module Types # behavior of Virtus which was used earlier, a `Grape::Validations::Types::PrimitiveCoercer` # maintains Virtus behavior in coercing. class ArrayCoercer < DryTypeCoercer + register_collection Array + def initialize(type, strict = false) super @coercer = scope::Array - @elem_coercer = PrimitiveCoercer.new(type.first, strict) + @subtype = type.first end def call(_val) collection = super - return collection if collection.is_a?(InvalidValue) coerce_elements collection @@ -31,13 +32,15 @@ def call(_val) protected + attr_reader :subtype + def coerce_elements(collection) return if collection.nil? collection.each_with_index do |elem, index| return InvalidValue.new if reject?(elem) - coerced_elem = @elem_coercer.call(elem) + coerced_elem = elem_coercer.call(elem) return coerced_elem if coerced_elem.is_a?(InvalidValue) @@ -47,11 +50,15 @@ def coerce_elements(collection) collection end - # This method maintaine logic which was defined by Virtus for arrays. + # This method maintains logic which was defined by Virtus for arrays. # Virtus doesn't allow nil in arrays. def reject?(val) val.nil? end + + def elem_coercer + @elem_coercer ||= DryTypeCoercer.coercer_instance_for(subtype, strict) + end end end end diff --git a/lib/grape/validations/types/build_coercer.rb b/lib/grape/validations/types/build_coercer.rb index b867485c1..c55e048db 100644 --- a/lib/grape/validations/types/build_coercer.rb +++ b/lib/grape/validations/types/build_coercer.rb @@ -60,12 +60,8 @@ def self.create_coercer_instance(type, method, strict) Types::CustomTypeCollectionCoercer.new( Types.map_special(type.first), type.is_a?(Set) ) - elsif type.is_a?(Array) - ArrayCoercer.new type, strict - elsif type.is_a?(Set) - SetCoercer.new type, strict else - PrimitiveCoercer.new type, strict + DryTypeCoercer.coercer_instance_for(type, strict) end end diff --git a/lib/grape/validations/types/dry_type_coercer.rb b/lib/grape/validations/types/dry_type_coercer.rb index 06d234d55..0a682e53e 100644 --- a/lib/grape/validations/types/dry_type_coercer.rb +++ b/lib/grape/validations/types/dry_type_coercer.rb @@ -17,8 +17,41 @@ module Types # but check its type. More information there # https://dry-rb.org/gems/dry-types/1.2/built-in-types/ class DryTypeCoercer + class << self + # Registers a collection coercer which could be found by a type, + # see +collection_coercer_for+ method below. This method is meant for inheritors. + def register_collection(type) + DryTypeCoercer.collection_coercers[type] = self + end + + # Returns a collection coercer which corresponds to a given type. + # Example: + # + # collection_coercer_for(Array) + # #=> Grape::Validations::Types::ArrayCoercer + def collection_coercer_for(type) + collection_coercers[type] + end + + # Returns an instance of a coercer for a given type + def coercer_instance_for(type, strict = false) + return PrimitiveCoercer.new(type, strict) if type.class == Class + + # in case of a collection (Array[Integer]) the type is an instance of a collection, + # so we need to figure out the actual type + collection_coercer_for(type.class).new(type, strict) + end + + protected + + def collection_coercers + @collection_coercers ||= {} + end + end + def initialize(type, strict = false) @type = type + @strict = strict @scope = strict ? DryTypes::Strict : DryTypes::Params end @@ -36,7 +69,7 @@ def call(val) protected - attr_reader :scope, :type + attr_reader :scope, :type, :strict end end end diff --git a/lib/grape/validations/types/set_coercer.rb b/lib/grape/validations/types/set_coercer.rb index 1ddc887c2..dc76fc773 100644 --- a/lib/grape/validations/types/set_coercer.rb +++ b/lib/grape/validations/types/set_coercer.rb @@ -1,18 +1,20 @@ # frozen_string_literal: true require 'set' -require_relative 'dry_type_coercer' +require_relative 'array_coercer' module Grape module Validations module Types # Takes the given array and converts it to a set. Every element of the set # is also coerced. - class SetCoercer < DryTypeCoercer + class SetCoercer < ArrayCoercer + register_collection Set + def initialize(type, strict = false) super - @elem_coercer = PrimitiveCoercer.new(type.first, strict) + @coercer = nil end def call(value) @@ -25,7 +27,7 @@ def call(value) def coerce_elements(collection) collection.each_with_object(Set.new) do |elem, memo| - coerced_elem = @elem_coercer.call(elem) + coerced_elem = elem_coercer.call(elem) return coerced_elem if coerced_elem.is_a?(InvalidValue) diff --git a/spec/grape/validations/types/array_coercer_spec.rb b/spec/grape/validations/types/array_coercer_spec.rb new file mode 100644 index 000000000..f2bfb6c6d --- /dev/null +++ b/spec/grape/validations/types/array_coercer_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Grape::Validations::Types::ArrayCoercer do + subject { described_class.new(type) } + + describe '#call' do + context 'an array of primitives' do + let(:type) { Array[String] } + + it 'coerces elements in the array' do + expect(subject.call([10, 20])).to eq(%w[10 20]) + end + end + + context 'an array of arrays' do + let(:type) { Array[Array[Integer]] } + + it 'coerces elements in the nested array' do + expect(subject.call([%w[10 20]])).to eq([[10, 20]]) + expect(subject.call([['10'], ['20']])).to eq([[10], [20]]) + end + end + + context 'an array of sets' do + let(:type) { Array[Set[Integer]] } + + it 'coerces elements in the nested set' do + expect(subject.call([%w[10 20]])).to eq([Set[10, 20]]) + expect(subject.call([['10'], ['20']])).to eq([Set[10], Set[20]]) + end + end + end +end diff --git a/spec/grape/validations/types/primitive_coercer_spec.rb b/spec/grape/validations/types/primitive_coercer_spec.rb index d0e1b137b..dbfd33826 100644 --- a/spec/grape/validations/types/primitive_coercer_spec.rb +++ b/spec/grape/validations/types/primitive_coercer_spec.rb @@ -7,7 +7,7 @@ subject { described_class.new(type, strict) } - describe '.call' do + describe '#call' do context 'Boolean' do let(:type) { Grape::API::Boolean } diff --git a/spec/grape/validations/types/set_coercer_spec.rb b/spec/grape/validations/types/set_coercer_spec.rb new file mode 100644 index 000000000..d78f5f511 --- /dev/null +++ b/spec/grape/validations/types/set_coercer_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Grape::Validations::Types::SetCoercer do + subject { described_class.new(type) } + + describe '#call' do + context 'a set of primitives' do + let(:type) { Set[String] } + + it 'coerces elements to the set' do + expect(subject.call([10, 20])).to eq(Set['10', '20']) + end + end + + context 'a set of sets' do + let(:type) { Set[Set[Integer]] } + + it 'coerces elements in the nested set' do + expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]]) + expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]]) + end + end + + context 'a set of sets of arrays' do + let(:type) { Set[Set[Array[Integer]]] } + + it 'coerces elements in the nested set' do + expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]]) + end + end + end +end From c141628871c65539672fe39ada47e389abfe4f1b Mon Sep 17 00:00:00 2001 From: Hermann Mayer Date: Thu, 14 May 2020 10:10:07 +0200 Subject: [PATCH 217/290] Fix broken multiple mounts. Signed-off-by: Hermann Mayer --- .rubocop_todo.yml | 17 ++++++----- CHANGELOG.md | 1 + lib/grape/api/instance.rb | 9 +++--- spec/grape/api/instance_spec.rb | 50 +++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b5140c65d..12131efbf 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-03-02 11:38:28 +0100 using RuboCop version 0.80.1. +# on 2020-05-18 14:31:59 +0200 using RuboCop version 0.83.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -13,7 +13,7 @@ Layout/ClosingHeredocIndentation: - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' -# Offense count: 72 +# Offense count: 71 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: Enabled: false @@ -65,9 +65,10 @@ Lint/ToJSON: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 48 +# Offense count: 47 +# Configuration parameters: IgnoredMethods. Metrics/AbcSize: - Max: 43 + Max: 44 # Offense count: 6 # Configuration parameters: CountComments, ExcludedMethods. @@ -80,14 +81,15 @@ Metrics/BlockLength: Metrics/ClassLength: Max: 305 -# Offense count: 32 +# Offense count: 30 +# Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: Max: 14 # Offense count: 61 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: - Max: 33 + Max: 36 # Offense count: 12 # Configuration parameters: CountComments. @@ -95,6 +97,7 @@ Metrics/ModuleLength: Max: 220 # Offense count: 25 +# Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: Max: 14 @@ -220,7 +223,7 @@ Style/WordArray: - 'spec/grape/validations/validators/except_values_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 1314 +# Offense count: 1339 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/CHANGELOG.md b/CHANGELOG.md index dd6324d7f..5eb9bc967 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * [#2043](https://github.com/ruby-grape/grape/pull/2043): Modify declared for nested array and hash - [@kadotami](https://github.com/kadotami). * [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx). * [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk). +* [#2050](https://github.com/ruby-grape/grape/pull/2053): Fix broken multiple mounts - [@Jack12816](https://github.com/Jack12816). * Your contribution here. ### 1.3.2 (2020/04/12) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index be86c6edc..922ce58bc 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -205,11 +205,12 @@ def add_head_not_allowed_methods_and_options_methods route_settings[:requirements] = route.requirements route_settings[:path] = route.origin route_settings[:methods] ||= [] - route_settings[:methods] << route.request_method + if route.request_method == '*' || route_settings[:methods].include?('*') + route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS + else + route_settings[:methods] << route.request_method + end route_settings[:endpoint] = route.app - - # using the :any shorthand produces [nil] for route methods, substitute all manually - route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS if route_settings[:methods].include?('*') end end diff --git a/spec/grape/api/instance_spec.rb b/spec/grape/api/instance_spec.rb index fa5c949e5..f0a278a7b 100644 --- a/spec/grape/api/instance_spec.rb +++ b/spec/grape/api/instance_spec.rb @@ -51,4 +51,54 @@ def app expect(an_instance.top_level_setting.parent).to be_nil end end + + context 'with multiple moutes' do + let(:first) do + Class.new(Grape::API::Instance) do + namespace(:some_namespace) do + route :any, '*path' do + error!('Not found! (1)', 404) + end + end + end + end + let(:second) do + Class.new(Grape::API::Instance) do + namespace(:another_namespace) do + route :any, '*path' do + error!('Not found! (2)', 404) + end + end + end + end + let(:root_api) do + first_instance = first + second_instance = second + Class.new(Grape::API) do + mount first_instance + mount first_instance + mount second_instance + end + end + + it 'does not raise a FrozenError on first instance' do + expect { patch '/some_namespace/anything' }.not_to \ + raise_error + end + + it 'responds the correct body at the first instance' do + patch '/some_namespace/anything' + expect(last_response.body).to eq 'Not found! (1)' + end + + it 'does not raise a FrozenError on second instance' do + expect { get '/another_namespace/other' }.not_to \ + raise_error + end + + it 'responds the correct body at the second instance' do + get '/another_namespace/foobar' + expect(last_response.body).to eq 'Not found! (2)' + end + end end From 536622a38ae5ed266477689cdd7afabd92046ed2 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 23 May 2020 09:19:52 +0300 Subject: [PATCH 218/290] Preparing for release, 1.3.3 --- CHANGELOG.md | 4 +--- README.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb9bc967..440dee4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.3.3 (Next) +### 1.3.3 (2020/05/23) #### Features @@ -6,7 +6,6 @@ * [#2039](https://github.com/ruby-grape/grape/pull/2039): Travis - update rails versions - [@ericproulx](https://github.com/ericproulx). * [#2038](https://github.com/ruby-grape/grape/pull/2038): Travis - update ruby versions - [@ericproulx](https://github.com/ericproulx). * [#2050](https://github.com/ruby-grape/grape/pull/2050): Refactor route public_send to AttributeTranslator - [@ericproulx](https://github.com/ericproulx). -* Your contribution here. #### Fixes @@ -15,7 +14,6 @@ * [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx). * [#2054](https://github.com/ruby-grape/grape/pull/2054): Coercing of nested arrays - [@dnesteryuk](https://github.com/dnesteryuk). * [#2050](https://github.com/ruby-grape/grape/pull/2053): Fix broken multiple mounts - [@Jack12816](https://github.com/Jack12816). -* Your contribution here. ### 1.3.2 (2020/04/12) diff --git a/README.md b/README.md index 37c762829..9107d3edf 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.3.3**. -Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.3.2](https://github.com/ruby-grape/grape/blob/v1.3.2/README.md). +You're reading the documentation for the stable release of Grape, **1.3.3**. ## Project Resources From 1e13356dea73212b717a118423411d317a1e4224 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 23 May 2020 09:21:45 +0300 Subject: [PATCH 219/290] Preparing for next development iteration, 1.3.4 --- CHANGELOG.md | 10 ++++++++++ README.md | 4 +++- lib/grape/version.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 440dee4f6..ed6cabc80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.3.4 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.3.3 (2020/05/23) #### Features diff --git a/README.md b/README.md index 9107d3edf..b43a9e7cf 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, **1.3.3**. +You're reading the documentation for the next release of Grape, which should be **1.3.4**. +Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.3.3](https://github.com/ruby-grape/grape/blob/v1.3.3/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index db729705f..42bcaef01 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.3.3' + VERSION = '1.3.4' end From 567755c18ab6d819a5107d22434752571277def4 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Mon, 25 May 2020 10:18:55 -0500 Subject: [PATCH 220/290] Add #1956 to changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6cabc80..0d98006c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,7 +62,8 @@ * [#1976](https://github.com/ruby-grape/grape/pull/1976): Ensure classes/modules listed for autoload really exist - [@dnesteryuk](https://github.com/dnesteryuk). * [#1971](https://github.com/ruby-grape/grape/pull/1971): Fix BigDecimal coercion - [@FlickStuart](https://github.com/FlickStuart). * [#1968](https://github.com/ruby-grape/grape/pull/1968): Fix args forwarding in Grape::Middleware::Stack#merge_with for ruby 2.7.0 - [@dm1try](https://github.com/dm1try). -* [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactored the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi). +* [#1988](https://github.com/ruby-grape/grape/pull/1988): Refactor the full_messages method and stop overriding full_message - [@hosseintoussi](https://github.com/hosseintoussi). +* [#1956](https://github.com/ruby-grape/grape/pull/1956): Comply with Rack spec, fix `undefined method [] for nil:NilClass` error when upgrading Rack - [@ioquatix](https://github.com/ioquatix). ### 1.3.0 (2020/01/11) From bed22d51f350e6c6a68ccb576260798102512e2d Mon Sep 17 00:00:00 2001 From: dblock Date: Tue, 26 May 2020 08:28:55 -0400 Subject: [PATCH 221/290] Upgraded to Rubocop 0.84.0. --- .rubocop_todo.yml | 4 ++-- CHANGELOG.md | 1 + Gemfile | 2 +- gemfiles/multi_json.gemfile | 2 +- gemfiles/multi_xml.gemfile | 2 +- gemfiles/rack1.gemfile | 2 +- gemfiles/rack2.gemfile | 2 +- gemfiles/rack_edge.gemfile | 2 +- gemfiles/rails_5.gemfile | 2 +- gemfiles/rails_6.gemfile | 2 +- gemfiles/rails_edge.gemfile | 2 +- spec/grape/middleware/auth/strategies_spec.rb | 2 +- 12 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 12131efbf..79cfdde38 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-05-18 14:31:59 +0200 using RuboCop version 0.83.0. +# on 2020-05-26 08:28:37 -0400 using RuboCop version 0.84.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -223,7 +223,7 @@ Style/WordArray: - 'spec/grape/validations/validators/except_values_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 1339 +# Offense count: 125 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d98006c7..078efed11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Features +* [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). * Your contribution here. #### Fixes diff --git a/Gemfile b/Gemfile index ecd0b7e0f..48a26eb17 100644 --- a/Gemfile +++ b/Gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index ed8a87bc9..0cb8517c1 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index e8598bd36..d352caf98 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile index 677bff97f..836f9c902 100644 --- a/gemfiles/rack1.gemfile +++ b/gemfiles/rack1.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rack2.gemfile b/gemfiles/rack2.gemfile index 4796d30a3..9f92d32ff 100644 --- a/gemfiles/rack2.gemfile +++ b/gemfiles/rack2.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index 9269e3ae5..185c39391 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 5de94c557..894d2bd50 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile index b08046c16..c7b9671f2 100644 --- a/gemfiles/rails_6.gemfile +++ b/gemfiles/rails_6.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index 295553ac7..24b5c5a8f 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -10,7 +10,7 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.80.1' + gem 'rubocop', '0.84.0' gem 'rubocop-performance', require: false end diff --git a/spec/grape/middleware/auth/strategies_spec.rb b/spec/grape/middleware/auth/strategies_spec.rb index 3917bc76e..954c99631 100644 --- a/spec/grape/middleware/auth/strategies_spec.rb +++ b/spec/grape/middleware/auth/strategies_spec.rb @@ -36,7 +36,7 @@ def app RSpec::Matchers.define :be_challenge do match do |actual_response| actual_response.status == 401 && - actual_response['WWW-Authenticate'] =~ /^Digest / && + actual_response['WWW-Authenticate'].start_with?('Digest ') && actual_response.body.empty? end end From d8b66d5bc7ca5446137936a9e223547116e03da9 Mon Sep 17 00:00:00 2001 From: dblock Date: Tue, 26 May 2020 08:38:46 -0400 Subject: [PATCH 222/290] Correct release version, closes #2058. --- UPGRADING.md | 73 +++++++++++++++++++--------------------------------- 1 file changed, 27 insertions(+), 46 deletions(-) diff --git a/UPGRADING.md b/UPGRADING.md index 8cdec92c8..74abd2244 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,16 +1,17 @@ Upgrading Grape =============== -### Upgrading to >= 1.4.0 +### Upgrading to >= 1.3.3 #### Nil values for structures Nil values always been a special case when dealing with types especially with the following structures: - - Array - - Hash - - Set - -The behaviour for these structures has change through out the latest releases. For instance: + +- Array +- Hash +- Set + +The behavior for these structures has change through out the latest releases. For example: ```ruby class Api < Grape::API @@ -26,9 +27,10 @@ class Api < Grape::API # 1.3.2 = nil end ``` + For now on, `nil` values stay `nil` values for all types, including arrays, sets and hashes. -If you want to have the same behavior as 1.3.1, apply a `default` validator +If you want to have the same behavior as 1.3.1, apply a `default` validator: ```ruby class Api < Grape::API @@ -68,10 +70,7 @@ After adding dry-types, Ruby 2.4 or newer is required. #### Coercion -[Virtus](https://github.com/solnic/virtus) has been replaced by -[dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter -coercion. If your project depends on Virtus outside of Grape, explicitly -add it to your `Gemfile`. +[Virtus](https://github.com/solnic/virtus) has been replaced by [dry-types](https://dry-rb.org/gems/dry-types/1.2/) for parameter coercion. If your project depends on Virtus outside of Grape, explicitly add it to your `Gemfile`. Here's an example of how to migrate a custom type from Virtus to dry-types: @@ -98,10 +97,7 @@ To use dry-types, we need to: 1. Rename `coerce` to `self.parse` 1. Rename `value_coerced?` to `self.parsed?` -The custom type must have a class-level `parse` method to the model. A -class-level `parsed?` is needed if the parsed type differs from the -defined type. In the example below, since `SecureUri` is not the same -as `URI::HTTPS`, `self.parsed?` is needed: +The custom type must have a class-level `parse` method to the model. A class-level `parsed?` is needed if the parsed type differs from the defined type. In the example below, since `SecureUri` is not the same as `URI::HTTPS`, `self.parsed?` is needed: ```ruby # New dry-types parser @@ -122,19 +118,13 @@ end #### Ensure that Array types have explicit coercions -Unlike Virtus, dry-types does not perform any implict coercions. If you -have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they -use a `coerce_with` block. For example: +Unlike Virtus, dry-types does not perform any implict coercions. If you have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they use a `coerce_with` block. For example: ```ruby requires :values, type: Array[String] ``` -It's quite common to pass a comma-separated list, such as `tag1,tag2` as -`values`. Previously Virtus would implicitly coerce this to -`Array(values)` so that `["tag1,tag2"]` would pass the type checks, but -with `dry-types` the values are no longer coerced for you. To fix this, -you might do: +It's quite common to pass a comma-separated list, such as `tag1,tag2` as `values`. Previously Virtus would implicitly coerce this to `Array(values)` so that `["tag1,tag2"]` would pass the type checks, but with `dry-types` the values are no longer coerced for you. To fix this, you might do: ```ruby requires :values, type: Array[String], coerce_with: ->(val) { val.split(',').map(&:strip) } @@ -201,12 +191,9 @@ In order to make obtaining the name of a mounted class simpler, we've delegated ##### Patching the class -In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, -rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced -with a class that can contain several instances of `Grape::API`. +In an effort to make APIs re-mountable, The class `Grape::API` no longer refers to an API instance, rather, what used to be `Grape::API` is now `Grape::API::Instance` and `Grape::API` was replaced with a class that can contain several instances of `Grape::API`. -This changes were done in such a way that no code-changes should be required. -However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`. +This changes were done in such a way that no code-changes should be required. However, if experiencing problems, or relying on private methods and internal behaviour too deeply, it is possible to restore the prior behaviour by replacing the references from `Grape::API` to `Grape::API::Instance`. Note, this is particularly relevant if you are opening the class `Grape::API` for modification. @@ -229,15 +216,20 @@ end After the patch, the mounted API is no longer a Named class inheriting from `Grape::API`, it is an anonymous class which inherit from `Grape::API::Instance`. + What this means in practice, is: + - Generally: you can access the named class from the instance calling the getter `base`. -- In particular: If you need the `name`, you can use `base`.`name` +- In particular: If you need the `name`, you can use `base`.`name`. **Deprecated** + ```ruby payload[:endpoint].options[:for].name ``` + **New** + ```ruby payload[:endpoint].options[:for].base.name ``` @@ -328,8 +320,7 @@ See [#1610](https://github.com/ruby-grape/grape/pull/1610) for more information. #### The `except`, `except_message`, and `proc` options of the `values` validator are deprecated. -The new `except_values` validator should be used in place of the `except` and `except_message` options of -the `values` validator. +The new `except_values` validator should be used in place of the `except` and `except_message` options of the `values` validator. Arity one Procs may now be used directly as the `values` option to explicitly test param values. @@ -405,9 +396,7 @@ get '/example' #=> before: 405, after: 404 #### Removed param processing from built-in OPTIONS handler -When a request is made to the built-in `OPTIONS` handler, only the `before` and `after` -callbacks associated with the resource will be run. The `before_validation` and -`after_validation` callbacks and parameter validations will be skipped. +When a request is made to the built-in `OPTIONS` handler, only the `before` and `after` callbacks associated with the resource will be run. The `before_validation` and `after_validation` callbacks and parameter validations will be skipped. See [#1505](https://github.com/ruby-grape/grape/pull/1505) for more information. @@ -428,8 +417,7 @@ See [#1510](https://github.com/ruby-grape/grape/pull/1510) for more information. #### The default status code for DELETE is now 204 instead of 200. -Breaking change: Sets the default response status code for a delete request to 204. -A status of 204 makes the response more distinguishable and therefore easier to handle on the client side, particularly because a DELETE request typically returns an empty body as the resource was deleted or voided. +Breaking change: Sets the default response status code for a delete request to 204. A status of 204 makes the response more distinguishable and therefore easier to handle on the client side, particularly because a DELETE request typically returns an empty body as the resource was deleted or voided. To achieve the old behavior, one has to set it explicitly: ```ruby @@ -607,18 +595,14 @@ See [#1114](https://github.com/ruby-grape/grape/pull/1114) for more information. #### Bypasses formatters when status code indicates no content -To be consistent with rack and it's handling of standard responses -associated with no content, both default and custom formatters will now +To be consistent with rack and it's handling of standard responses associated with no content, both default and custom formatters will now be bypassed when processing responses for status codes defined [by rack](https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L567) See [#1190](https://github.com/ruby-grape/grape/pull/1190) for more information. #### Redirects respond as plain text with message -`#redirect` now uses `text/plain` regardless of whether that format has -been enabled. This prevents formatters from attempting to serialize the -message body and allows for a descriptive message body to be provided - and -optionally overridden - that better fulfills the theme of the HTTP spec. +`#redirect` now uses `text/plain` regardless of whether that format has been enabled. This prevents formatters from attempting to serialize the message body and allows for a descriptive message body to be provided - and optionally overridden - that better fulfills the theme of the HTTP spec. See [#1194](https://github.com/ruby-grape/grape/pull/1194) for more information. @@ -652,10 +636,7 @@ end See [#1029](https://github.com/ruby-grape/grape/pull/1029) for more information. -There is a known issue because of this change. When Grape is used with an older -than 1.2.4 version of [warden](https://github.com/hassox/warden) there may be raised -the following exception having the [rack-mount](https://github.com/jm/rack-mount) gem's -lines as last ones in the backtrace: +There is a known issue because of this change. When Grape is used with an older than 1.2.4 version of [warden](https://github.com/hassox/warden) there may be raised the following exception having the [rack-mount](https://github.com/jm/rack-mount) gem's lines as last ones in the backtrace: ``` NoMethodError: undefined method `[]' for nil:NilClass From 7aecebb15813dba586abae7f84a30d7738a20453 Mon Sep 17 00:00:00 2001 From: dblock Date: Tue, 26 May 2020 09:46:39 -0400 Subject: [PATCH 223/290] Drop support for Ruby 2.4. --- .travis.yml | 4 ---- CHANGELOG.md | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82bd2e647..1939eb156 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,10 +47,6 @@ matrix: gemfile: gemfiles/rails_5.gemfile - rvm: 2.5.8 gemfile: gemfiles/rails_6.gemfile - - rvm: 2.4.10 - gemfile: Gemfile - - rvm: 2.4.10 - gemfile: gemfiles/rails_5.gemfile - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 078efed11..135c7443e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Features +* [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). * Your contribution here. From b996ab20f92adf62f8eccea7c5ff12640576366e Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Tue, 26 May 2020 20:26:32 -0400 Subject: [PATCH 224/290] Fix order of paragraphs in docs (#2063) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b43a9e7cf..9ac342fc3 100644 --- a/README.md +++ b/README.md @@ -1059,13 +1059,13 @@ params do end ``` -Note that default values will be passed through to any validation options specified. -The following example will always fail if `:color` is not explicitly provided. - Default values are eagerly evaluated. Above `:non_random_number` will evaluate to the same number for each call to the endpoint of this `params` block. To have the default evaluate lazily with each request use a lambda, like `:random_number` above. +Note that default values will be passed through to any validation options specified. +The following example will always fail if `:color` is not explicitly provided. + ```ruby params do optional :color, type: String, default: 'blue', values: ['red', 'green'] From 74619618b7bf18bb7fafac569b39f2b54ce919bf Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Tue, 26 May 2020 21:38:01 -0400 Subject: [PATCH 225/290] Add note about an undocumented regression in bool coercion (#2062) --- UPGRADING.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/UPGRADING.md b/UPGRADING.md index 74abd2244..2917d2793 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -116,6 +116,22 @@ params do end ``` +#### Coercing to `FalseClass` or `TrueClass` no longer works + +Previous Grape versions allowed this, though it wasn't documented: + +```ruby +requires :true_value, type: TrueClass +requires :bool_value, types: [FalseClass, TrueClass] +``` + +This is no longer supported, if you do this, your values will never be valid. Instead you should do this: + +```ruby +requires :true_value, type: Boolean # in your endpoint you should validate if this is actually `true` +requires :bool_value, type: Boolean +``` + #### Ensure that Array types have explicit coercions Unlike Virtus, dry-types does not perform any implict coercions. If you have any uses of `Array[String]`, `Array[Integer]`, etc. be sure they use a `coerce_with` block. For example: From 2bf2c1a67c87683bcb6beb4b0a5f02900967b2e4 Mon Sep 17 00:00:00 2001 From: Stephen Karger Date: Fri, 29 May 2020 17:17:43 -0400 Subject: [PATCH 226/290] Treat Middleware::Base#initialize options as Hash. (#2064) --- CHANGELOG.md | 1 + lib/grape/middleware/base.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 135c7443e..eec65e074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Fixes * Your contribution here. +* [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger). ### 1.3.3 (2020/05/23) diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 19be12d4c..2b5657e73 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -14,9 +14,9 @@ class Base # @param [Rack Application] app The standard argument for a Rack middleware. # @param [Hash] options A hash of options, simply stored for use by subclasses. - def initialize(app, **options) + def initialize(app, options = {}) @app = app - @options = default_options.merge(**options) + @options = default_options.merge(options) @app_response = nil end From bfe7d5c4353bf83c4195c733fba010cd610ff160 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 8 Jun 2020 00:45:25 -0700 Subject: [PATCH 227/290] Add a benchmark for API.compile! This replicates the slowness in compiling routes via Mustermann for 2000 routes. Part of https://github.com/ruby-grape/grape/issues/1736 --- benchmark/compile_many_routes.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 benchmark/compile_many_routes.rb diff --git a/benchmark/compile_many_routes.rb b/benchmark/compile_many_routes.rb new file mode 100644 index 000000000..9fa858cf3 --- /dev/null +++ b/benchmark/compile_many_routes.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +require 'grape' +require 'benchmark/ips' +require 'grape/eager_load' + +Grape.eager_load! + +class API < Grape::API + prefix :api + version 'v1', using: :path + + 2000.times do |index| + get "/test#{index}/" do + 'hello' + end + end +end + +Benchmark.ips do |ips| + ips.report('Compiling 2000 routes') do + API.compile! + end +end From 83960b05c2274deeea97778af15ab5054f353356 Mon Sep 17 00:00:00 2001 From: Pete Kinnecom Date: Mon, 8 Jun 2020 18:33:56 -0700 Subject: [PATCH 228/290] Coerce empty string to nil for all Primitive types except String (#2067) * Reorder PrimitiveCoercerSpec contexts to be alphabetical * Coerce empty string to nil for all Primitive types except String This matches the behavior in v1.2.5 with the exception of Symbol. In v1.2.5 an empty string was coerced to an empty symbol (:''), now it is coerced to nil. * Add integration specs for empty string param coercion These specs mirror the specs for nil params. Exceptions are made for non-primitive types based on their current behavior. --- CHANGELOG.md | 1 + .../validations/types/primitive_coercer.rb | 3 +- .../types/primitive_coercer_spec.rb | 64 +++++++++++++- .../validations/validators/coerce_spec.rb | 86 +++++++++++++++++++ 4 files changed, 148 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eec65e074..b1f1d1c76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ #### Fixes * Your contribution here. +* [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty string to nil for all primitive types except String - [@petekinnecom](https://github.com/petekinnecom). * [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger). ### 1.3.3 (2020/05/23) diff --git a/lib/grape/validations/types/primitive_coercer.rb b/lib/grape/validations/types/primitive_coercer.rb index b7656e39a..7b1e84180 100644 --- a/lib/grape/validations/types/primitive_coercer.rb +++ b/lib/grape/validations/types/primitive_coercer.rb @@ -37,7 +37,6 @@ def initialize(type, strict = false) def call(val) return InvalidValue.new if reject?(val) return nil if val.nil? || treat_as_nil?(val) - return '' if val == '' super end @@ -60,7 +59,7 @@ def reject?(val) # absence of a value and coerces it into nil. See a discussion there # https://github.com/ruby-grape/grape/pull/2045 def treat_as_nil?(val) - val == '' && type == Grape::API::Boolean + val == '' && type != String end end end diff --git a/spec/grape/validations/types/primitive_coercer_spec.rb b/spec/grape/validations/types/primitive_coercer_spec.rb index dbfd33826..df375ac37 100644 --- a/spec/grape/validations/types/primitive_coercer_spec.rb +++ b/spec/grape/validations/types/primitive_coercer_spec.rb @@ -8,6 +8,18 @@ subject { described_class.new(type, strict) } describe '#call' do + context 'BigDecimal' do + let(:type) { BigDecimal } + + it 'coerces to BigDecimal' do + expect(subject.call(5)).to eq(BigDecimal(5)) + end + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end + end + context 'Boolean' do let(:type) { Grape::API::Boolean } @@ -32,19 +44,63 @@ end end + context 'DateTime' do + let(:type) { DateTime } + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end + end + + context 'Float' do + let(:type) { Float } + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end + end + + context 'Integer' do + let(:type) { Integer } + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end + end + + context 'Numeric' do + let(:type) { Numeric } + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end + end + + context 'Time' do + let(:type) { Time } + + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil + end + end + context 'String' do let(:type) { String } it 'coerces to String' do expect(subject.call(10)).to eq('10') end + + it 'does not coerce an empty string to nil' do + expect(subject.call('')).to eq('') + end end - context 'BigDecimal' do - let(:type) { BigDecimal } + context 'Symbol' do + let(:type) { Symbol } - it 'coerces to BigDecimal' do - expect(subject.call(5)).to eq(BigDecimal(5)) + it 'coerces an empty string to nil' do + expect(subject.call('')).to be_nil end end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index e6537ff54..41acb5f0d 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -497,6 +497,92 @@ def self.parsed?(value) end end end + + context 'empty string' do + context 'primitive types' do + (Grape::Validations::Types::PRIMITIVES - [String]).each do |type| + it "is coerced to nil for type #{type}" do + subject.params do + requires :param, type: type + end + subject.get '/empty_string' do + params[:param].class + end + + get '/empty_string', param: '' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + + it 'is not coerced to nil for type String' do + subject.params do + requires :param, type: String + end + subject.get '/empty_string' do + params[:param].class + end + + get '/empty_string', param: '' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('String') + end + end + + context 'structures types' do + (Grape::Validations::Types::STRUCTURES - [Hash]).each do |type| + it "is coerced to nil for type #{type}" do + subject.params do + requires :param, type: type + end + subject.get '/empty_string' do + params[:param].class + end + + get '/empty_string', param: '' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + end + + context 'special types' do + (Grape::Validations::Types::SPECIAL.keys - [File, Rack::Multipart::UploadedFile]).each do |type| + it "is coerced to nil for type #{type}" do + subject.params do + requires :param, type: type + end + subject.get '/empty_string' do + params[:param].class + end + + get '/empty_string', param: '' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + + context 'variant-member-type collections' do + [ + Array[Integer, String], + [Integer, String, Array[Integer, String]] + ].each do |type| + it "is coerced to nil for type #{type}" do + subject.params do + requires :param, type: type + end + subject.get '/empty_string' do + params[:param].class + end + + get '/empty_string', param: '' + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + end + end + end + end end context 'using coerce_with' do From 3db3b0a8bf353a435ca6a5628ed2bd503a16b670 Mon Sep 17 00:00:00 2001 From: Edward Rudd Date: Mon, 14 Nov 2016 17:06:15 -0500 Subject: [PATCH 229/290] un-deprecate stream-like objects - deprecate file and replace with sendfile - update stream to not deprecate stream-like objects --- CHANGELOG.md | 3 +- README.md | 28 ++- UPGRADING.md | 64 ++++++ lib/grape.rb | 4 +- lib/grape/dsl/inside_route.rb | 40 +++- lib/grape/endpoint.rb | 6 +- lib/grape/middleware/formatter.rb | 6 +- .../{serve_file => serve_stream}/file_body.rb | 2 +- .../sendfile_response.rb | 2 +- .../stream_response.rb} | 16 +- lib/grape/version.rb | 2 +- spec/grape/dsl/inside_route_spec.rb | 208 +++++++++++++++--- spec/grape/integration/rack_sendfile_spec.rb | 20 +- spec/grape/middleware/formatter_spec.rb | 2 +- 14 files changed, 329 insertions(+), 74 deletions(-) rename lib/grape/{serve_file => serve_stream}/file_body.rb (96%) rename lib/grape/{serve_file => serve_stream}/sendfile_response.rb (95%) rename lib/grape/{serve_file/file_response.rb => serve_stream/stream_response.rb} (58%) diff --git a/CHANGELOG.md b/CHANGELOG.md index eec65e074..d3a4d12d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ -### 1.3.4 (Next) +### 1.4.0 (Next) #### Features +* [#1520](https://github.com/ruby-grape/grape/pull/1520): Un-deprecate stream-like objects - [@urkle](https://github.com/urkle). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). * Your contribution here. diff --git a/README.md b/README.md index 9ac342fc3..97ed66840 100644 --- a/README.md +++ b/README.md @@ -3168,17 +3168,19 @@ end Use `body false` to return `204 No Content` without any data or content-type. -You can also set the response to a file with `file`. +You can also set the response to a file with `sendfile`. This works with the +[Rack::Sendfile](https://www.rubydoc.info/gems/rack/Rack/Sendfile) middleware to optimally send +the file through your web server software. ```ruby class API < Grape::API get '/' do - file '/path/to/file' + sendfile '/path/to/file' end end ``` -If you want a file to be streamed using Rack::Chunked, use `stream`. +To stream a file in chunks use `stream` ```ruby class API < Grape::API @@ -3188,6 +3190,26 @@ class API < Grape::API end ``` +If you want to stream non-file data use the `stream` method and a `Stream` object. +This is an object that responds to `each` and yields for each chunk to send to the client. +Each chunk will be sent as it is yielded instead of waiting for all of the content to be available. + +```ruby +class MyStream + def each + yield 'part 1' + yield 'part 2' + yield 'part 3' + end +end + +class API < Grape::API + get '/' do + stream MyStream.new + end +end +``` + ## Authentication ### Basic and Digest Auth diff --git a/UPGRADING.md b/UPGRADING.md index 2917d2793..591fa51ad 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,70 @@ Upgrading Grape =============== +### Upgrading to >= 1.4.0 + +#### Reworking stream and file and un-deprecating stream like-objects + +Previously in 0.16 stream-like objects were deprecated. This release restores their functionality for use-cases other than file streaming. + +This release deprecated `file` in favor of `sendfile` to better document its purpose. + +To deliver a file via the Sendfile support in your web server and have the Rack::Sendfile middleware enabled. See [`Rack::Sendfile`](https://www.rubydoc.info/gems/rack/Rack/Sendfile). +```ruby +class API < Grape::API + get '/' do + sendfile '/path/to/file' + end +end +``` + +Use `stream` to stream file content in chunks. + +```ruby +class API < Grape::API + get '/' do + stream '/path/to/file' + end +end +``` + +Or use `stream` to stream other kinds of content. In the following example a streamer class +streams paginated data from a database. + +```ruby +class MyObject + attr_accessor :result + + def initialize(query) + @result = query + end + + def each + yield '[' + # Do paginated DB fetches and return each page formatted + first = false + result.find_in_batches do |records| + yield process_records(records, first) + first = false + end + yield ']' + end + + def process_records(records, first) + buffer = +'' + buffer << ',' unless first + buffer << records.map(&:to_json).join(',') + buffer + end +end + +class API < Grape::API + get '/' do + stream MyObject.new(Sprocket.all) + end +end +``` + ### Upgrading to >= 1.3.3 #### Nil values for structures diff --git a/lib/grape.rb b/lib/grape.rb index 3ba532fbc..e2a0b9808 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -206,12 +206,12 @@ module Presenters end end - module ServeFile + module ServeStream extend ::ActiveSupport::Autoload eager_autoload do - autoload :FileResponse autoload :FileBody autoload :SendfileResponse + autoload :StreamResponse end end end diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 7739db845..cf218a4c8 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -279,23 +279,36 @@ def return_no_content body false end - # Allows you to define the response as a file-like object. + # Deprecated method to send files to the client. Use `sendfile` or `stream` + def file(value = nil) + if value.is_a?(String) + warn '[DEPRECATION] Use sendfile or stream to send files.' + sendfile(value) + elsif !value.is_a?(NilClass) + warn '[DEPRECATION] Use stream to use a Stream object.' + stream(value) + else + warn '[DEPRECATION] Use sendfile or stream to send files.' + sendfile + end + end + + # Allows you to send a file to the client via sendfile. # # @example # get '/file' do - # file FileStreamer.new(...) + # sendfile FileStreamer.new(...) # end # # GET /file # => "contents of file" - def file(value = nil) + def sendfile(value = nil) if value.is_a?(String) - file_body = Grape::ServeFile::FileBody.new(value) - @file = Grape::ServeFile::FileResponse.new(file_body) + file_body = Grape::ServeStream::FileBody.new(value) + @stream = Grape::ServeStream::StreamResponse.new(file_body) elsif !value.is_a?(NilClass) - warn '[DEPRECATION] Argument as FileStreamer-like object is deprecated. Use path to file instead.' - @file = Grape::ServeFile::FileResponse.new(value) + raise ArgumentError, 'Argument must be a file path' else - instance_variable_defined?(:@file) ? @file : nil + stream end end @@ -318,7 +331,16 @@ def stream(value = nil) header 'Content-Length', nil header 'Transfer-Encoding', nil header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front) - file(value) + if value.is_a?(String) + file_body = Grape::ServeStream::FileBody.new(value) + @stream = Grape::ServeStream::StreamResponse.new(file_body) + elsif value.respond_to?(:each) + @stream = Grape::ServeStream::StreamResponse.new(value) + elsif !value.is_a?(NilClass) + raise ArgumentError, 'Stream object must respond to :each.' + else + instance_variable_defined?(:@stream) ? @stream : nil + end end # Allows you to make use of Grape Entities by setting diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 3fb417874..b202c8271 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -99,7 +99,7 @@ def initialize(new_settings, options = {}, &block) @block = nil @status = nil - @file = nil + @stream = nil @body = nil @proc = nil @@ -271,8 +271,8 @@ def run # status verifies body presence when DELETE @body ||= response_object - # The body commonly is an Array of Strings, the application instance itself, or a File-like object - response_object = file || [body] + # The body commonly is an Array of Strings, the application instance itself, or a Stream-like object + response_object = stream || [body] [status, header, response_object] ensure diff --git a/lib/grape/middleware/formatter.rb b/lib/grape/middleware/formatter.rb index 7ea7e57af..bb9888c2a 100644 --- a/lib/grape/middleware/formatter.rb +++ b/lib/grape/middleware/formatter.rb @@ -36,9 +36,9 @@ def after def build_formatted_response(status, headers, bodies) headers = ensure_content_type(headers) - if bodies.is_a?(Grape::ServeFile::FileResponse) - Grape::ServeFile::SendfileResponse.new([], status, headers) do |resp| - resp.body = bodies.file + if bodies.is_a?(Grape::ServeStream::StreamResponse) + Grape::ServeStream::SendfileResponse.new([], status, headers) do |resp| + resp.body = bodies.stream end else # Allow content-type to be explicitly overwritten diff --git a/lib/grape/serve_file/file_body.rb b/lib/grape/serve_stream/file_body.rb similarity index 96% rename from lib/grape/serve_file/file_body.rb rename to lib/grape/serve_stream/file_body.rb index da4dd9e3f..b842af661 100644 --- a/lib/grape/serve_file/file_body.rb +++ b/lib/grape/serve_stream/file_body.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Grape - module ServeFile + module ServeStream CHUNK_SIZE = 16_384 # Class helps send file through API diff --git a/lib/grape/serve_file/sendfile_response.rb b/lib/grape/serve_stream/sendfile_response.rb similarity index 95% rename from lib/grape/serve_file/sendfile_response.rb rename to lib/grape/serve_stream/sendfile_response.rb index 5ff723b96..b46fc102a 100644 --- a/lib/grape/serve_file/sendfile_response.rb +++ b/lib/grape/serve_stream/sendfile_response.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Grape - module ServeFile + module ServeStream # Response should respond to to_path method # for using Rack::SendFile middleware class SendfileResponse < Rack::Response diff --git a/lib/grape/serve_file/file_response.rb b/lib/grape/serve_stream/stream_response.rb similarity index 58% rename from lib/grape/serve_file/file_response.rb rename to lib/grape/serve_stream/stream_response.rb index 88928d33c..0705fbf7b 100644 --- a/lib/grape/serve_file/file_response.rb +++ b/lib/grape/serve_stream/stream_response.rb @@ -1,22 +1,22 @@ # frozen_string_literal: true module Grape - module ServeFile - # A simple class used to identify responses which represent files and do not + module ServeStream + # A simple class used to identify responses which represent streams (or files) and do not # need to be formatted or pre-read by Rack::Response - class FileResponse - attr_reader :file + class StreamResponse + attr_reader :stream - # @param file [Object] - def initialize(file) - @file = file + # @param stream [Object] + def initialize(stream) + @stream = stream end # Equality provided mostly for tests. # # @return [Boolean] def ==(other) - file == other.file + stream == other.stream end end end diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 42bcaef01..c9c3d7cc6 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.3.4' + VERSION = '1.4.0' end diff --git a/spec/grape/dsl/inside_route_spec.rb b/spec/grape/dsl/inside_route_spec.rb index 51da952fc..7c37d8f69 100644 --- a/spec/grape/dsl/inside_route_spec.rb +++ b/spec/grape/dsl/inside_route_spec.rb @@ -203,80 +203,222 @@ def initialize end describe '#file' do + before do + allow(subject).to receive(:warn) + end + describe 'set' do context 'as file path' do let(:file_path) { '/some/file/path' } - let(:file_response) do - file_body = Grape::ServeFile::FileBody.new(file_path) - Grape::ServeFile::FileResponse.new(file_body) - end + it 'emits a warning that this method is deprecated' do + expect(subject).to receive(:warn).with(/Use sendfile or stream/) - before do subject.file file_path end - it 'returns value wrapped in FileResponse' do - expect(subject.file).to eq file_response + it 'forwards the call to sendfile' do + expect(subject).to receive(:sendfile).with(file_path) + + subject.file file_path end end context 'as object (backward compatibility)' do - let(:file_object) { Class.new } + let(:file_object) { double('StreamerObject', each: nil) } + + it 'emits a warning that this method is deprecated' do + expect(subject).to receive(:warn).with(/Use stream to use a Stream object/) + + subject.file file_object + end + + it 'forwards the call to stream' do + expect(subject).to receive(:stream).with(file_object) + + subject.file file_object + end + end + end + + describe 'get' do + it 'emits a warning that this method is deprecated' do + expect(subject).to receive(:warn).with(/Use sendfile or stream/) + + subject.file + end + + it 'fowards call to sendfile' do + expect(subject).to receive(:sendfile) + + subject.file + end + end + end + + describe '#sendfile' do + describe 'set' do + context 'as file path' do + let(:file_path) { '/some/file/path' } let(:file_response) do - Grape::ServeFile::FileResponse.new(file_object) + file_body = Grape::ServeStream::FileBody.new(file_path) + Grape::ServeStream::StreamResponse.new(file_body) end before do - subject.file file_object + subject.header 'Cache-Control', 'cache' + subject.header 'Content-Length', 123 + subject.header 'Transfer-Encoding', 'base64' + end + + it 'sends no deprecation warnings' do + expect(subject).to_not receive(:warn) + + subject.sendfile file_path + end + + it 'returns value wrapped in StreamResponse' do + subject.sendfile file_path + + expect(subject.sendfile).to eq file_response + end + + it 'does not change the Cache-Control header' do + subject.sendfile file_path + + expect(subject.header['Cache-Control']).to eq 'cache' + end + + it 'does not change the Content-Length header' do + subject.sendfile file_path + + expect(subject.header['Content-Length']).to eq 123 + end + + it 'does not change the Transfer-Encoding header' do + subject.sendfile file_path + + expect(subject.header['Transfer-Encoding']).to eq 'base64' end + end + + context 'as object' do + let(:file_object) { double('StreamerObject', each: nil) } - it 'returns value wrapped in FileResponse' do - expect(subject.file).to eq file_response + it 'raises an error that only a file path is supported' do + expect { subject.sendfile file_object }.to raise_error(ArgumentError, /Argument must be a file path/) end end end it 'returns default' do - expect(subject.file).to be nil + expect(subject.sendfile).to be nil end end describe '#stream' do describe 'set' do - let(:file_object) { Class.new } + context 'as a file path' do + let(:file_path) { '/some/file/path' } - before do - subject.header 'Cache-Control', 'cache' - subject.header 'Content-Length', 123 - subject.header 'Transfer-Encoding', 'base64' - subject.stream file_object - end + let(:file_response) do + file_body = Grape::ServeStream::FileBody.new(file_path) + Grape::ServeStream::StreamResponse.new(file_body) + end - it 'returns value wrapped in FileResponse' do - expect(subject.stream).to eq Grape::ServeFile::FileResponse.new(file_object) - end + before do + subject.header 'Cache-Control', 'cache' + subject.header 'Content-Length', 123 + subject.header 'Transfer-Encoding', 'base64' + end - it 'also sets result of file to value wrapped in FileResponse' do - expect(subject.file).to eq Grape::ServeFile::FileResponse.new(file_object) - end + it 'emits no deprecation warnings' do + expect(subject).to_not receive(:warn) + + subject.stream file_path + end + + it 'returns file body wrapped in StreamResponse' do + subject.stream file_path + + expect(subject.stream).to eq file_response + end + + it 'sets Cache-Control header to no-cache' do + subject.stream file_path + + expect(subject.header['Cache-Control']).to eq 'no-cache' + end + + it 'sets Content-Length header to nil' do + subject.stream file_path + + expect(subject.header['Content-Length']).to eq nil + end + + it 'sets Transfer-Encoding header to nil' do + subject.stream file_path - it 'sets Cache-Control header to no-cache' do - expect(subject.header['Cache-Control']).to eq 'no-cache' + expect(subject.header['Transfer-Encoding']).to eq nil + end end - it 'sets Content-Length header to nil' do - expect(subject.header['Content-Length']).to eq nil + context 'as a stream object' do + let(:stream_object) { double('StreamerObject', each: nil) } + + let(:stream_response) do + Grape::ServeStream::StreamResponse.new(stream_object) + end + + before do + subject.header 'Cache-Control', 'cache' + subject.header 'Content-Length', 123 + subject.header 'Transfer-Encoding', 'base64' + end + + it 'emits no deprecation warnings' do + expect(subject).to_not receive(:warn) + + subject.stream stream_object + end + + it 'returns value wrapped in StreamResponse' do + subject.stream stream_object + + expect(subject.stream).to eq stream_response + end + + it 'sets Cache-Control header to no-cache' do + subject.stream stream_object + + expect(subject.header['Cache-Control']).to eq 'no-cache' + end + + it 'sets Content-Length header to nil' do + subject.stream stream_object + + expect(subject.header['Content-Length']).to eq nil + end + + it 'sets Transfer-Encoding header to nil' do + subject.stream stream_object + + expect(subject.header['Transfer-Encoding']).to eq nil + end end - it 'sets Transfer-Encoding header to nil' do - expect(subject.header['Transfer-Encoding']).to eq nil + context 'as a non-stream object' do + let(:non_stream_object) { double('NonStreamerObject') } + + it 'raises an error that the object must implement :each' do + expect { subject.stream non_stream_object }.to raise_error(ArgumentError, /:each/) + end end end it 'returns default' do - expect(subject.file).to be nil + expect(subject.stream).to be nil end end diff --git a/spec/grape/integration/rack_sendfile_spec.rb b/spec/grape/integration/rack_sendfile_spec.rb index 8b4f34feb..ab7cb1ba0 100644 --- a/spec/grape/integration/rack_sendfile_spec.rb +++ b/spec/grape/integration/rack_sendfile_spec.rb @@ -4,12 +4,16 @@ describe Rack::Sendfile do subject do - send_file = file_streamer + content_object = file_object app = Class.new(Grape::API) do use Rack::Sendfile format :json get do - file send_file + if content_object.is_a?(String) + sendfile content_object + else + stream content_object + end end end @@ -22,9 +26,9 @@ app.call(env) end - context do - let(:file_streamer) do - double(:file_streamer, to_path: '/accel/mapping/some/path') + context 'when calling sendfile' do + let(:file_object) do + '/accel/mapping/some/path' end it 'contains Sendfile headers' do @@ -33,9 +37,9 @@ end end - context do - let(:file_streamer) do - double(:file_streamer) + context 'when streaming non file content' do + let(:file_object) do + double(:file_object, each: nil) end it 'not contains Sendfile headers' do diff --git a/spec/grape/middleware/formatter_spec.rb b/spec/grape/middleware/formatter_spec.rb index 7c73663b5..9f6333cd0 100644 --- a/spec/grape/middleware/formatter_spec.rb +++ b/spec/grape/middleware/formatter_spec.rb @@ -380,7 +380,7 @@ def to_xml context 'send file' do let(:file) { double(File) } - let(:file_body) { Grape::ServeFile::FileResponse.new(file) } + let(:file_body) { Grape::ServeStream::StreamResponse.new(file) } let(:app) { ->(_env) { [200, {}, file_body] } } it 'returns a file response' do From 12786e61de10b808e4e3693112546cdbd78c9f31 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 31 May 2020 20:49:35 +0300 Subject: [PATCH 230/290] simplify logic for defining declared params Now, route settings get declared params when an endpoint gets initialized, the `Grape::Validations::ParamsScope` doesn't need to worry about that anymore. Since the endpoint takes care of finalizing declared params, the `declared_params` method works with a structure which doesn't require any processing. --- CHANGELOG.md | 1 + lib/grape/dsl/inside_route.rb | 4 ++-- lib/grape/dsl/validations.rb | 19 ++++++++++++++++- lib/grape/endpoint.rb | 8 ++++--- lib/grape/validations/params_scope.rb | 6 +++--- spec/grape/validations_spec.rb | 30 +++++++++++++++------------ 6 files changed, 46 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d661b9129..37928bf29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * [#1520](https://github.com/ruby-grape/grape/pull/1520): Un-deprecate stream-like objects - [@urkle](https://github.com/urkle). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). +* [#2077](https://github.com/ruby-grape/grape/pull/2077): Simplify logic for defining declared params - [@dnesteryuk](https://github.com/dnesteryuk). * Your contribution here. #### Fixes diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index cf218a4c8..7fcf54549 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -124,10 +124,10 @@ def route_options_params_key(params_nested_path) def optioned_declared_params(**options) declared_params = if options[:include_parent_namespaces] # Declared params including parent namespaces - route_setting(:saved_declared_params).flatten | Array(route_setting(:declared_params)) + route_setting(:declared_params) else # Declared params at current namespace - route_setting(:saved_declared_params).last & Array(route_setting(:declared_params)) + namespace_stackable(:declared_params).last || [] end raise ArgumentError, 'Tried to filter for declared parameters but none exist.' unless declared_params diff --git a/lib/grape/dsl/validations.rb b/lib/grape/dsl/validations.rb index a426a11aa..d2d354fb1 100644 --- a/lib/grape/dsl/validations.rb +++ b/lib/grape/dsl/validations.rb @@ -10,7 +10,24 @@ module Validations include Grape::DSL::Configuration module ClassMethods - # Clears all defined parameters and validations. + # Clears all defined parameters and validations. The main purpose of it is to clean up + # settings, so next endpoint won't interfere with previous one. + # + # params do + # # params for the endpoint below this block + # end + # post '/current' do + # # whatever + # end + # + # # somewhere between them the reset_validations! method gets called + # + # params do + # # params for the endpoint below this block + # end + # post '/next' do + # # whatever + # end def reset_validations! unset_namespace_stackable :declared_params unset_namespace_stackable :validations diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index b202c8271..7a8439692 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -80,7 +80,10 @@ def initialize(new_settings, options = {}, &block) self.inheritable_setting = new_settings.point_in_time_copy - route_setting(:saved_declared_params, namespace_stackable(:declared_params)) + # now +namespace_stackable(:declared_params)+ contains all params defined for + # this endpoint and its parents, but later it will be cleaned up, + # see +reset_validations!+ in lib/grape/dsl/validations.rb + route_setting(:declared_params, namespace_stackable(:declared_params).flatten) route_setting(:saved_validations, namespace_stackable(:validations)) namespace_stackable(:representations, []) unless namespace_stackable(:representations) @@ -116,7 +119,6 @@ def inherit_settings(namespace_stackable) parent_declared_params = namespace_stackable[:declared_params] if parent_declared_params - inheritable_setting.route[:declared_params] ||= [] inheritable_setting.route[:declared_params].concat(parent_declared_params.flatten) end @@ -190,7 +192,7 @@ def prepare_default_route_attributes requirements: prepare_routes_requirements, prefix: namespace_inheritable(:root_prefix), anchor: options[:route_options].fetch(:anchor, true), - settings: inheritable_setting.route.except(:saved_declared_params, :saved_validations), + settings: inheritable_setting.route.except(:declared_params, :saved_validations), forward_match: options[:forward_match] } end diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 8a0c84cbb..84e72f8bc 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -237,10 +237,10 @@ def configure_declared_params @parent.push_declared_params [element => @declared_params] else @api.namespace_stackable(:declared_params, @declared_params) - - @api.route_setting(:declared_params, []) unless @api.route_setting(:declared_params) - @api.route_setting(:declared_params, @api.namespace_stackable(:declared_params).flatten) end + + # params were stored in settings, it can be cleaned from the params scope + @declared_params = nil end def validates(attrs, validations) diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 7bde6104a..232c23534 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -9,6 +9,10 @@ def app subject end + def declared_params + subject.namespace_stackable(:declared_params).flatten + end + describe 'params' do context 'optional' do before do @@ -41,7 +45,7 @@ def app subject.params do optional :some_param end - expect(subject.route_setting(:declared_params)).to eq([:some_param]) + expect(declared_params).to eq([:some_param]) end end @@ -61,7 +65,7 @@ def define_optional_using it 'adds entity documentation to declared params' do define_optional_using - expect(subject.route_setting(:declared_params)).to eq(%i[field_a field_b]) + expect(declared_params).to eq(%i[field_a field_b]) end it 'works when field_a and field_b are not present' do @@ -108,7 +112,7 @@ def define_optional_using subject.params do requires :some_param end - expect(subject.route_setting(:declared_params)).to eq([:some_param]) + expect(declared_params).to eq([:some_param]) end it 'works when required field is present but nil' do @@ -193,7 +197,7 @@ def define_requires_all it 'adds entity documentation to declared params' do define_requires_all - expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field]) + expect(declared_params).to eq(%i[required_field optional_field]) end it 'errors when required_field is not present' do @@ -228,7 +232,7 @@ def define_requires_none it 'adds entity documentation to declared params' do define_requires_none - expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field]) + expect(declared_params).to eq(%i[required_field optional_field]) end it 'errors when required_field is not present' do @@ -258,7 +262,7 @@ def define_requires_all it 'adds only the entity documentation to declared params, nothing more' do define_requires_all - expect(subject.route_setting(:declared_params)).to eq(%i[required_field optional_field]) + expect(declared_params).to eq(%i[required_field optional_field]) end end @@ -324,7 +328,7 @@ def define_requires_none requires :key end end - expect(subject.route_setting(:declared_params)).to eq([items: [:key]]) + expect(declared_params).to eq([items: [:key]]) end end @@ -396,7 +400,7 @@ def define_requires_none requires :key end end - expect(subject.route_setting(:declared_params)).to eq([items: [:key]]) + expect(declared_params).to eq([items: [:key]]) end end @@ -459,7 +463,7 @@ def define_requires_none requires :key end end - expect(subject.route_setting(:declared_params)).to eq([items: [:key]]) + expect(declared_params).to eq([items: [:key]]) end end @@ -813,7 +817,7 @@ def validate_param!(attr_name, params) requires :key end end - expect(subject.route_setting(:declared_params)).to eq([items: [:key]]) + expect(declared_params).to eq([items: [:key]]) end end @@ -877,7 +881,7 @@ def validate_param!(attr_name, params) requires(:required_subitems, type: Array) { requires :value } end end - expect(subject.route_setting(:declared_params)).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]]) + expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]]) end end @@ -1122,14 +1126,14 @@ def validate_param!(attr_name, params) subject.params do use :pagination end - expect(subject.route_setting(:declared_params)).to eq %i[page per_page] + expect(declared_params).to eq %i[page per_page] end it 'by #use with multiple params' do subject.params do use :pagination, :period end - expect(subject.route_setting(:declared_params)).to eq %i[page per_page start_date end_date] + expect(declared_params).to eq %i[page per_page start_date end_date] end end From b3adbb4060f81a14f1cba35cabe796d35a442bff Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 11 Jun 2020 22:21:17 -0700 Subject: [PATCH 231/290] Fix eager loading due to ServeStream rename ServeFile was renamed to ServeStream in #1520. Calling Grape.eager_load! would fail with: ``` uninitialized constant Grape::ServeFile (NameError) ``` --- .travis.yml | 4 ++++ CHANGELOG.md | 4 +++- lib/grape/eager_load.rb | 2 +- spec/integration/eager_load/eager_load_spec.rb | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 spec/integration/eager_load/eager_load_spec.rb diff --git a/.travis.yml b/.travis.yml index 1939eb156..134679cfe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,10 @@ matrix: gemfile: gemfiles/rails_5.gemfile - rvm: 2.7.1 gemfile: gemfiles/rails_6.gemfile + - rvm: 2.7.1 + gemfile: Gemfile + script: + - bundle exec rspec spec/integration/eager_load - rvm: 2.7.1 gemfile: gemfiles/multi_json.gemfile script: diff --git a/CHANGELOG.md b/CHANGELOG.md index 37928bf29..05a0cf331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ #### Features +* [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix eager loading due to ServeStream rename - [@stanhu](https://github.com/stanhu). * [#1520](https://github.com/ruby-grape/grape/pull/1520): Un-deprecate stream-like objects - [@urkle](https://github.com/urkle). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). @@ -10,9 +11,10 @@ #### Fixes -* Your contribution here. * [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty string to nil for all primitive types except String - [@petekinnecom](https://github.com/petekinnecom). * [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger). +* [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix `Grape.eager_load!` and `compile!` - [@stanhu](https://github.com/stanhu). +* Your contribution here. ### 1.3.3 (2020/05/23) diff --git a/lib/grape/eager_load.rb b/lib/grape/eager_load.rb index 91baa9e57..ef7bc3ec7 100644 --- a/lib/grape/eager_load.rb +++ b/lib/grape/eager_load.rb @@ -16,5 +16,5 @@ Grape::DSL.eager_load! Grape::API.eager_load! Grape::Presenters.eager_load! -Grape::ServeFile.eager_load! +Grape::ServeStream.eager_load! Rack::Head # AutoLoads the Rack::Head diff --git a/spec/integration/eager_load/eager_load_spec.rb b/spec/integration/eager_load/eager_load_spec.rb new file mode 100644 index 000000000..94b78e3e0 --- /dev/null +++ b/spec/integration/eager_load/eager_load_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib')) +require 'grape' + +describe Grape do + it 'eager_load!' do + require 'grape/eager_load' + expect { Grape.eager_load! }.to_not raise_error + end + + it 'compile!' do + expect { Class.new(Grape::API).compile! }.to_not raise_error + end +end From a5de05121556aed79b351846fc5e650ad91388b7 Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 28 Jun 2020 09:31:56 -0400 Subject: [PATCH 232/290] Removed duplicate CHANGELOG line. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a0cf331..aa43c2cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ #### Features -* [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix eager loading due to ServeStream rename - [@stanhu](https://github.com/stanhu). * [#1520](https://github.com/ruby-grape/grape/pull/1520): Un-deprecate stream-like objects - [@urkle](https://github.com/urkle). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). From c532d648eea16a08e4e7aeab2ef4bfe8a8985962 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Fri, 26 Jun 2020 23:49:01 +0300 Subject: [PATCH 233/290] Populate the env route information when calling the generated allow_headers endpoints --- CHANGELOG.md | 1 + lib/grape/api/instance.rb | 48 +++++++++++++---------------- lib/grape/router.rb | 35 +++++++++++---------- spec/grape/api_spec.rb | 65 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa43c2cb8..c50d10928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty string to nil for all primitive types except String - [@petekinnecom](https://github.com/petekinnecom). * [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger). * [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix `Grape.eager_load!` and `compile!` - [@stanhu](https://github.com/stanhu). +* [#2076](https://github.com/ruby-grape/grape/pull/2076): Make route information available for hooks when the automatically generated endpoints are invoked - [@anakinj](https://github.com/anakinj). * Your contribution here. ### 1.3.3 (2020/05/23) diff --git a/lib/grape/api/instance.rb b/lib/grape/api/instance.rb index 922ce58bc..e8f8d85fb 100644 --- a/lib/grape/api/instance.rb +++ b/lib/grape/api/instance.rb @@ -192,37 +192,15 @@ def cascade? # will return an HTTP 405 response for any HTTP method that the resource # cannot handle. def add_head_not_allowed_methods_and_options_methods - routes_map = {} - - self.class.endpoints.each do |endpoint| - routes = endpoint.routes - routes.each do |route| - # using the :any shorthand produces [nil] for route methods, substitute all manually - route_key = route.pattern.to_regexp - routes_map[route_key] ||= {} - route_settings = routes_map[route_key] - route_settings[:pattern] = route.pattern - route_settings[:requirements] = route.requirements - route_settings[:path] = route.origin - route_settings[:methods] ||= [] - if route.request_method == '*' || route_settings[:methods].include?('*') - route_settings[:methods] = Grape::Http::Headers::SUPPORTED_METHODS - else - route_settings[:methods] << route.request_method - end - route_settings[:endpoint] = route.app - end - end - + versioned_route_configs = collect_route_config_per_pattern # The paths we collected are prepared (cf. Path#prepare), so they # contain already versioning information when using path versioning. # Disable versioning so adding a route won't prepend versioning # informations again. without_root_prefix do without_versioning do - routes_map.each_value do |config| - methods = config[:methods] - allowed_methods = methods.dup + versioned_route_configs.each do |config| + allowed_methods = config[:methods].dup unless self.class.namespace_inheritable(:do_not_route_head) allowed_methods |= [Grape::Http::Headers::HEAD] if allowed_methods.include?(Grape::Http::Headers::GET) @@ -241,6 +219,25 @@ def add_head_not_allowed_methods_and_options_methods end end + def collect_route_config_per_pattern + all_routes = self.class.endpoints.map(&:routes).flatten + routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp } + + # Build the configuration based on the first endpoint and the collection of methods supported. + routes_by_regexp.values.map do |routes| + last_route = routes.last # Most of the configuration is taken from the last endpoint + matching_wildchar = routes.any? { |route| route.request_method == '*' } + { + options: {}, + pattern: last_route.pattern, + requirements: last_route.requirements, + path: last_route.origin, + endpoint: last_route.app, + methods: matching_wildchar ? Grape::Http::Headers::SUPPORTED_METHODS : routes.map(&:request_method) + } + end + end + # Generate a route that returns an HTTP 405 response for a user defined # path on methods not specified def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) @@ -252,7 +249,6 @@ def generate_not_allowed_method(pattern, allowed_methods: [], **attributes) end not_allowed_methods = supported_methods - allowed_methods return if not_allowed_methods.empty? - @router.associate_routes(pattern, not_allowed_methods: not_allowed_methods, **attributes) end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index 26c7f1a0d..f2b8cf241 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -99,37 +99,34 @@ def transaction(env) response = yield(input, method) return response if response && !(cascade = cascade?(response)) - neighbor = greedy_match?(input) + last_neighbor_route = greedy_match?(input) - # If neighbor exists and request method is OPTIONS, + # If last_neighbor_route exists and request method is OPTIONS, # return response by using #call_with_allow_headers. - return call_with_allow_headers( - env, - neighbor.allow_header, - neighbor.endpoint - ) if neighbor && method == Grape::Http::Headers::OPTIONS && !cascade + return call_with_allow_headers(env, last_neighbor_route) if last_neighbor_route && method == Grape::Http::Headers::OPTIONS && !cascade route = match?(input, '*') - return neighbor.endpoint.call(env) if neighbor && cascade && route + + return last_neighbor_route.endpoint.call(env) if last_neighbor_route && cascade && route if route response = process_route(route, env) return response if response && !(cascade = cascade?(response)) end - !cascade && neighbor ? call_with_allow_headers(env, neighbor.allow_header, neighbor.endpoint) : nil + return call_with_allow_headers(env, last_neighbor_route) if !cascade && last_neighbor_route + + nil end def process_route(route, env) - input, = *extract_input_and_method(env) - routing_args = env[Grape::Env::GRAPE_ROUTING_ARGS] - env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(routing_args, route, input) + prepare_env_from_route(env, route) route.exec(env) end def make_routing_args(default_args, route, input) args = default_args || { route_info: route } - args.merge(route.params(input)) + args.merge(route.params(input) || {}) end def extract_input_and_method(env) @@ -160,9 +157,15 @@ def greedy_match?(input) @neutral_map.detect { |route| last_match["_#{route.index}"] } end - def call_with_allow_headers(env, methods, endpoint) - env[Grape::Env::GRAPE_ALLOWED_METHODS] = methods.join(', ').freeze - endpoint.call(env) + def call_with_allow_headers(env, route) + prepare_env_from_route(env, route) + env[Grape::Env::GRAPE_ALLOWED_METHODS] = route.allow_header.join(', ').freeze + route.endpoint.call(env) + end + + def prepare_env_from_route(env, route) + input, = *extract_input_and_method(env) + env[Grape::Env::GRAPE_ROUTING_ARGS] = make_routing_args(env[Grape::Env::GRAPE_ROUTING_ARGS], route, input) end def cascade?(response) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 43abeffbe..8b3af2710 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -816,6 +816,71 @@ class DummyFormatClass end end + describe 'when hook behaviour is controlled by attributes on the route ' do + before do + subject.before do + error!('Access Denied', 401) unless route.options[:secret] == params[:secret] + end + + subject.namespace 'example' do + before do + error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret] + end + + desc 'it gets with secret', secret: 'password' + get { status(params[:id] == '504' ? 200 : 404) } + + desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password' + post {} + end + end + + context 'when HTTP method is not defined' do + let(:response) { delete('/example') } + + it 'responds with a 405 status' do + expect(response.status).to eql 405 + end + end + + context 'when HTTP method is defined with attribute' do + let(:response) { post('/example?secret=incorrect_password') } + it 'responds with the defined error in the before hook' do + expect(response.status).to eql 401 + end + end + + context 'when HTTP method is defined and the underlying before hook expectation is not met' do + let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') } + it 'ends up in the endpoint' do + expect(response.status).to eql 401 + end + end + + context 'when HTTP method is defined and everything is like the before hooks expect' do + let(:response) { post('/example?secret=password&namespace_secret=namespace_password') } + it 'ends up in the endpoint' do + expect(response.status).to eql 201 + end + end + + context 'when HEAD is called for the defined GET' do + let(:response) { head('/example?id=504') } + + it 'responds with 401 because before expectations in before hooks are not met' do + expect(response.status).to eql 401 + end + end + + context 'when HEAD is called for the defined GET' do + let(:response) { head('/example?id=504&secret=password') } + + it 'responds with 200 because before hooks are not called' do + expect(response.status).to eql 200 + end + end + end + context 'allows HEAD on a GET request that' do before do subject.get 'example' do From 35d528f207a77be4c99c62fd3f5b231aaf6554e4 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Fri, 10 Jul 2020 12:56:40 +0100 Subject: [PATCH 234/290] Fix memory leak caused by caching all unique paths seen This optimisation was introduced in #2002. If an api's paths include an id in the path then this cache can grow in an unbounded manner. --- CHANGELOG.md | 1 + lib/grape/router.rb | 18 +++++------------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c50d10928..6b7d2e28d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). * [#2077](https://github.com/ruby-grape/grape/pull/2077): Simplify logic for defining declared params - [@dnesteryuk](https://github.com/dnesteryuk). +* [#2084](https://github.com/ruby-grape/grape/pull/2084): Fix memory leak in path normalization - [@fcheung](https://github.com/fcheung). * Your contribution here. #### Fixes diff --git a/lib/grape/router.rb b/lib/grape/router.rb index f2b8cf241..c5d9e9642 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -7,20 +7,12 @@ module Grape class Router attr_reader :map, :compiled - class NormalizePathCache < Grape::Util::Cache - def initialize - @cache = Hash.new do |h, path| - normalized_path = +"/#{path}" - normalized_path.squeeze!('/') - normalized_path.sub!(%r{/+\Z}, '') - normalized_path = '/' if normalized_path.empty? - h[path] = -normalized_path - end - end - end - def self.normalize_path(path) - NormalizePathCache[path] + path = +"/#{path}" + path.squeeze!('/') + path.sub!(%r{/+\Z}, '') + path = '/' if path == '' + path end def self.supported_methods From b125a72cdbc499db03eedba1e03d049950a5185f Mon Sep 17 00:00:00 2001 From: dblock Date: Fri, 10 Jul 2020 15:01:01 -0400 Subject: [PATCH 235/290] Preparing for release, 1.4.0. --- CHANGELOG.md | 4 +--- README.md | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b7d2e28d..0b0a618e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.4.0 (Next) +### 1.4.0 (2020/07/10) #### Features @@ -7,7 +7,6 @@ * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). * [#2077](https://github.com/ruby-grape/grape/pull/2077): Simplify logic for defining declared params - [@dnesteryuk](https://github.com/dnesteryuk). * [#2084](https://github.com/ruby-grape/grape/pull/2084): Fix memory leak in path normalization - [@fcheung](https://github.com/fcheung). -* Your contribution here. #### Fixes @@ -15,7 +14,6 @@ * [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger). * [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix `Grape.eager_load!` and `compile!` - [@stanhu](https://github.com/stanhu). * [#2076](https://github.com/ruby-grape/grape/pull/2076): Make route information available for hooks when the automatically generated endpoints are invoked - [@anakinj](https://github.com/anakinj). -* Your contribution here. ### 1.3.3 (2020/05/23) diff --git a/README.md b/README.md index 97ed66840..74dd94d9d 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,8 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.3.4**. +You're reading the documentation for the stable release of Grape, 1.4.0. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.3.3](https://github.com/ruby-grape/grape/blob/v1.3.3/README.md). ## Project Resources From 84d68bd3f92a3cffcb3bced8b387bf98779d61ef Mon Sep 17 00:00:00 2001 From: dblock Date: Fri, 10 Jul 2020 15:03:22 -0400 Subject: [PATCH 236/290] Preparing for next developer iteration, 1.4.1. --- CHANGELOG.md | 10 ++++++++++ README.md | 3 ++- lib/grape/version.rb | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b0a618e3..6bff427ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.4.1 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.4.0 (2020/07/10) #### Features diff --git a/README.md b/README.md index 74dd94d9d..53769cada 100644 --- a/README.md +++ b/README.md @@ -156,8 +156,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, 1.4.0. +You're reading the documentation for the next release of Grape, which should be **1.4.1**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.4.0](https://github.com/ruby-grape/grape/blob/v1.4.0/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index c9c3d7cc6..615c251ab 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.4.0' + VERSION = '1.4.1' end From 28dde32d7ef9ae47e64b2e2c500a3256f5a6eb2b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 10 Jul 2020 16:07:42 -0700 Subject: [PATCH 237/290] Set Cache-Control only for streamed responses The pull request #1520 introduced a regression that always caused the `Cache-Control` HTTP header to be set to `no-cache`, even if the response wasn't a stream. To fix this, we only set HTTP headers if there is an actual stream. Closes https://github.com/ruby-grape/grape/issues/2087 --- CHANGELOG.md | 2 +- lib/grape/dsl/inside_route.rb | 4 +++- spec/grape/api_spec.rb | 5 +++++ spec/grape/dsl/inside_route_spec.rb | 7 +++++++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bff427ed..1410a655d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ #### Fixes -* Your contribution here. +* [#2083](https://github.com/ruby-grape/grape/pull/2083): Set Cache-Control only for streamed responses - [@stanhu](https://github.com/stanhu). ### 1.4.0 (2020/07/10) diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 7fcf54549..dc8f9f05c 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -328,6 +328,8 @@ def sendfile(value = nil) # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/chunked.rb # * https://github.com/rack/rack/blob/99293fa13d86cd48021630fcc4bd5acc9de5bdc3/lib/rack/etag.rb def stream(value = nil) + return if value.nil? && @stream.nil? + header 'Content-Length', nil header 'Transfer-Encoding', nil header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front) @@ -339,7 +341,7 @@ def stream(value = nil) elsif !value.is_a?(NilClass) raise ArgumentError, 'Stream object must respond to :each.' else - instance_variable_defined?(:@stream) ? @stream : nil + @stream end end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 8b3af2710..4d9325671 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1149,6 +1149,11 @@ class DummyFormatClass expect(last_response.headers['Content-Type']).to eq('text/plain') end + it 'does not set Cache-Control' do + get '/foo' + expect(last_response.headers['Cache-Control']).to eq(nil) + end + it 'sets content type for xml' do get '/foo.xml' expect(last_response.headers['Content-Type']).to eq('application/xml') diff --git a/spec/grape/dsl/inside_route_spec.rb b/spec/grape/dsl/inside_route_spec.rb index 7c37d8f69..9d83c148d 100644 --- a/spec/grape/dsl/inside_route_spec.rb +++ b/spec/grape/dsl/inside_route_spec.rb @@ -351,6 +351,12 @@ def initialize expect(subject.header['Cache-Control']).to eq 'no-cache' end + it 'does not change Cache-Control header' do + subject.stream + + expect(subject.header['Cache-Control']).to eq 'cache' + end + it 'sets Content-Length header to nil' do subject.stream file_path @@ -419,6 +425,7 @@ def initialize it 'returns default' do expect(subject.stream).to be nil + expect(subject.header['Cache-Control']).to eq nil end end From 028ca053d260638bc97a32e0464e0fa5535869c6 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 13 Jul 2020 23:57:35 -0400 Subject: [PATCH 238/290] Put CHANGELOG entries in correct sections. [ci skip] --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1410a655d..013d0fce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ #### Fixes -* [#2083](https://github.com/ruby-grape/grape/pull/2083): Set Cache-Control only for streamed responses - [@stanhu](https://github.com/stanhu). +* [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). ### 1.4.0 (2020/07/10) @@ -16,14 +16,14 @@ * [#2060](https://github.com/ruby-grape/grape/pull/2060): Drop support for Ruby 2.4 - [@dblock](https://github.com/dblock). * [#2060](https://github.com/ruby-grape/grape/pull/2060): Upgraded Rubocop to 0.84.0 - [@dblock](https://github.com/dblock). * [#2077](https://github.com/ruby-grape/grape/pull/2077): Simplify logic for defining declared params - [@dnesteryuk](https://github.com/dnesteryuk). -* [#2084](https://github.com/ruby-grape/grape/pull/2084): Fix memory leak in path normalization - [@fcheung](https://github.com/fcheung). +* [#2076](https://github.com/ruby-grape/grape/pull/2076): Make route information available for hooks when the automatically generated endpoints are invoked - [@anakinj](https://github.com/anakinj). #### Fixes -* [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty string to nil for all primitive types except String - [@petekinnecom](https://github.com/petekinnecom). +* [#2067](https://github.com/ruby-grape/grape/pull/2067): Coerce empty String to `nil` for all primitive types except `String` - [@petekinnecom](https://github.com/petekinnecom). * [#2064](https://github.com/ruby-grape/grape/pull/2064): Fix Ruby 2.7 deprecation warning in `Grape::Middleware::Base#initialize` - [@skarger](https://github.com/skarger). * [#2072](https://github.com/ruby-grape/grape/pull/2072): Fix `Grape.eager_load!` and `compile!` - [@stanhu](https://github.com/stanhu). -* [#2076](https://github.com/ruby-grape/grape/pull/2076): Make route information available for hooks when the automatically generated endpoints are invoked - [@anakinj](https://github.com/anakinj). +* [#2084](https://github.com/ruby-grape/grape/pull/2084): Fix memory leak in path normalization - [@fcheung](https://github.com/fcheung). ### 1.3.3 (2020/05/23) From e7e25010016f70b2a293c2095d7eaf7346e3d3ae Mon Sep 17 00:00:00 2001 From: Jonathan Chan Date: Tue, 14 Jul 2020 08:52:57 -0400 Subject: [PATCH 239/290] updating README to fix 404 error issue mounting using Cascade (#2089) Co-authored-by: Jonathan Chan --- CHANGELOG.md | 2 ++ README.md | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 013d0fce0..0c4e1d48c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ #### Fixes +* Your contribution here. +* [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). * [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). ### 1.4.0 (2020/07/10) diff --git a/README.md b/README.md index 53769cada..3641af707 100644 --- a/README.md +++ b/README.md @@ -350,9 +350,12 @@ class Web < Sinatra::Base end use Rack::Session::Cookie -run Rack::Cascade.new [API, Web] +run Rack::Cascade.new [Web, API] ``` +Note that order of loading apps using `Rack::Cascade` matters. The grape application must be last if you want to raise custom 404 errors from grape (such as `error!('Not Found',404)`). If the grape application is not last and returns 404 or 405 response, [cascade utilizes that as a signal to try the next app](https://www.rubydoc.info/gems/rack/Rack/Cascade). This may lead to undesirable behavior showing the [wrong 404 page from the wrong app](https://github.com/ruby-grape/grape/issues/1515). + + ### Rails Place API files into `app/api`. Rails expects a subdirectory that matches the name of the Ruby module and a file name that matches the name of the class. In our example, the file name location and directory for `Twitter::API` should be `app/api/twitter/api.rb`. From 3e57355640bf6f96998222c9ef4f78c7f04c6e81 Mon Sep 17 00:00:00 2001 From: Huy Vo Date: Tue, 4 Aug 2020 08:02:42 +0700 Subject: [PATCH 240/290] Improve include missing doc --- CHANGELOG.md | 1 + README.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c4e1d48c..53d02d599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Your contribution here. * [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). * [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). +* [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc). ### 1.4.0 (2020/07/10) diff --git a/README.md b/README.md index 3641af707..c3a5ce73b 100644 --- a/README.md +++ b/README.md @@ -908,8 +908,10 @@ By default `declared(params)` includes parameters that have `nil` values. If you format :json params do - requires :first_name, type: String - optional :last_name, type: String + requires :user, type: Hash do + requires :first_name, type: String + optional :last_name, type: String + end end post 'users/signup' do From 6deb8f46b9c4057be59006c85cf317b5d64c6180 Mon Sep 17 00:00:00 2001 From: Dimitrij Denissenko Date: Mon, 10 Aug 2020 16:12:07 +0200 Subject: [PATCH 241/290] Fix Ruby 2.7 keyword deprecations (#2091) --- CHANGELOG.md | 1 + lib/grape/api.rb | 2 +- lib/grape/dsl/routing.rb | 6 ++--- lib/grape/middleware/base.rb | 2 +- lib/grape/middleware/error.rb | 22 +++++++++---------- lib/grape/middleware/stack.rb | 21 ++++++++++++++---- lib/grape/request.rb | 2 +- lib/grape/router.rb | 2 +- lib/grape/router/attribute_translator.rb | 4 ++-- lib/grape/util/base_inheritable.rb | 4 ++-- lib/grape/validations/validators/as.rb | 2 +- lib/grape/validations/validators/base.rb | 6 ++--- lib/grape/validations/validators/default.rb | 6 ++--- .../validations/validators/except_values.rb | 2 +- lib/grape/validations/validators/values.rb | 2 +- spec/grape/middleware/error_spec.rb | 2 +- spec/grape/middleware/stack_spec.rb | 3 ++- spec/support/versioned_helpers.rb | 8 +++---- 18 files changed, 51 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53d02d599..5054c1dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). * [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). * [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc). +* [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim). ### 1.4.0 (2020/07/10) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 507b22187..45dd76c74 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -90,7 +90,7 @@ def const_missing(*args) # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration # too much, you may actually want to provide a new API rather than remount it. - def mount_instance(opts = {}) + def mount_instance(**opts) instance = Class.new(@base_parent) instance.configuration = Grape::Util::EndpointConfiguration.new(opts[:configuration] || {}) instance.base = self diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index b2f955cc0..76a22a79f 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -79,7 +79,7 @@ def do_not_route_options! namespace_inheritable(:do_not_route_options, true) end - def mount(mounts, opts = {}) + def mount(mounts, **opts) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:mount_instance) @@ -170,9 +170,7 @@ def namespace(space = nil, options = {}, &block) previous_namespace_description = @namespace_description @namespace_description = (@namespace_description || {}).deep_merge(namespace_setting(:description) || {}) nest(block) do - if space - namespace_stackable(:namespace, Namespace.new(space, **options)) - end + namespace_stackable(:namespace, Namespace.new(space, **options)) if space end @namespace_description = previous_namespace_description end diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 2b5657e73..e21a94e9e 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -14,7 +14,7 @@ class Base # @param [Rack Application] app The standard argument for a Rack middleware. # @param [Hash] options A hash of options, simply stored for use by subclasses. - def initialize(app, options = {}) + def initialize(app, **options) @app = app @options = default_options.merge(options) @app_response = nil diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 6d816f14f..54bded3c8 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -19,11 +19,11 @@ def default_options rescue_subclasses: true, # rescue subclasses of exceptions listed rescue_options: { backtrace: false, # true to display backtrace, true to let Grape handle Grape::Exceptions - original_exception: false, # true to display exception + original_exception: false # true to display exception }, rescue_handlers: {}, # rescue handler blocks base_only_rescue_handlers: {}, # rescue handler blocks rescuing only the base class - all_rescue_handler: nil, # rescue handler block to rescue from all exceptions + all_rescue_handler: nil # rescue handler block to rescue from all exceptions } end @@ -38,15 +38,15 @@ def call!(env) error_response(catch(:error) do return @app.call(@env) end) - rescue Exception => error # rubocop:disable Lint/RescueException + rescue Exception => e # rubocop:disable Lint/RescueException handler = - rescue_handler_for_base_only_class(error.class) || - rescue_handler_for_class_or_its_ancestor(error.class) || - rescue_handler_for_grape_exception(error.class) || - rescue_handler_for_any_class(error.class) || + rescue_handler_for_base_only_class(e.class) || + rescue_handler_for_class_or_its_ancestor(e.class) || + rescue_handler_for_grape_exception(e.class) || + rescue_handler_for_any_class(e.class) || raise - run_rescue_handler(handler, error) + run_rescue_handler(handler, e) end end @@ -65,15 +65,13 @@ def error_response(error = {}) message = error[:message] || options[:default_message] headers = { Grape::Http::Headers::CONTENT_TYPE => content_type } headers.merge!(error[:headers]) if error[:headers].is_a?(Hash) - backtrace = error[:backtrace] || error[:original_exception] && error[:original_exception].backtrace || [] + backtrace = error[:backtrace] || error[:original_exception]&.backtrace || [] original_exception = error.is_a?(Exception) ? error : error[:original_exception] || nil rack_response(format_message(message, backtrace, original_exception), status, headers) end def rack_response(message, status = options[:default_status], headers = { Grape::Http::Headers::CONTENT_TYPE => content_type }) - if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML - message = ERB::Util.html_escape(message) - end + message = ERB::Util.html_escape(message) if headers[Grape::Http::Headers::CONTENT_TYPE] == TEXT_HTML Rack::Response.new([message], status, headers) end diff --git a/lib/grape/middleware/stack.rb b/lib/grape/middleware/stack.rb index 488a51498..b725e8787 100644 --- a/lib/grape/middleware/stack.rb +++ b/lib/grape/middleware/stack.rb @@ -6,11 +6,12 @@ module Middleware # It allows to insert and insert after class Stack class Middleware - attr_reader :args, :block, :klass + attr_reader :args, :opts, :block, :klass - def initialize(klass, *args, &block) + def initialize(klass, *args, **opts, &block) @klass = klass - @args = args + @args = args + @opts = opts @block = block end @@ -30,6 +31,18 @@ def ==(other) def inspect klass.to_s end + + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7') + def use_in(builder) + block ? builder.use(klass, *args, **opts, &block) : builder.use(klass, *args, **opts) + end + else + def use_in(builder) + args = self.args + args += [opts] unless opts.empty? + block ? builder.use(klass, *args, &block) : builder.use(klass, *args) + end + end end include Enumerable @@ -90,7 +103,7 @@ def merge_with(middleware_specs) def build(builder = Rack::Builder.new) others.shift(others.size).each(&method(:merge_with)) middlewares.each do |m| - m.block ? builder.use(m.klass, *m.args, &m.block) : builder.use(m.klass, *m.args) + m.use_in(builder) end builder end diff --git a/lib/grape/request.rb b/lib/grape/request.rb index 62a6cd314..b54779748 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -8,7 +8,7 @@ class Request < Rack::Request alias rack_params params - def initialize(env, options = {}) + def initialize(env, **options) extend options[:build_params_with] || Grape.config.param_builder super(env) end diff --git a/lib/grape/router.rb b/lib/grape/router.rb index c5d9e9642..8c5dce872 100644 --- a/lib/grape/router.rb +++ b/lib/grape/router.rb @@ -47,7 +47,7 @@ def append(route) def associate_routes(pattern, **options) @neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}") - @neutral_map << Grape::Router::AttributeTranslator.new(options.merge(pattern: pattern, index: @neutral_map.length)) + @neutral_map << Grape::Router::AttributeTranslator.new(**options, pattern: pattern, index: @neutral_map.length) end def call(env) diff --git a/lib/grape/router/attribute_translator.rb b/lib/grape/router/attribute_translator.rb index 93d112fcb..88003887c 100644 --- a/lib/grape/router/attribute_translator.rb +++ b/lib/grape/router/attribute_translator.rb @@ -23,7 +23,7 @@ class AttributeTranslator ROUTER_ATTRIBUTES = %i[pattern index].freeze - def initialize(attributes = {}) + def initialize(**attributes) @attributes = attributes end @@ -37,7 +37,7 @@ def to_h attributes end - def method_missing(method_name, *args) # rubocop:disable Style/MethodMissing + def method_missing(method_name, *args) if setter?(method_name[-1]) attributes[method_name[0..-1]] = *args else diff --git a/lib/grape/util/base_inheritable.rb b/lib/grape/util/base_inheritable.rb index d5c86beed..5db6e3455 100644 --- a/lib/grape/util/base_inheritable.rb +++ b/lib/grape/util/base_inheritable.rb @@ -9,8 +9,8 @@ class BaseInheritable # @param inherited_values [Object] An object implementing an interface # of the Hash class. - def initialize(inherited_values = {}) - @inherited_values = inherited_values + def initialize(inherited_values = nil) + @inherited_values = inherited_values || {} @new_values = {} end diff --git a/lib/grape/validations/validators/as.rb b/lib/grape/validations/validators/as.rb index 07ac8006d..77cef5f1c 100644 --- a/lib/grape/validations/validators/as.rb +++ b/lib/grape/validations/validators/as.rb @@ -3,7 +3,7 @@ module Grape module Validations class AsValidator < Base - def initialize(attrs, options, required, scope, opts = {}) + def initialize(attrs, options, required, scope, **opts) @renamed_options = options super end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 034a66e5a..4799f4923 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -13,7 +13,7 @@ class Base # @param required [Boolean] attribute(s) are required or optional # @param scope [ParamsScope] parent scope for this Validator # @param opts [Hash] additional validation options - def initialize(attrs, options, required, scope, opts = {}) + def initialize(attrs, options, required, scope, **opts) @attrs = Array(attrs) @option = options @required = required @@ -47,9 +47,7 @@ def validate!(params) next if !@scope.required? && empty_val next unless @scope.meets_dependency?(val, params) begin - if @required || val.respond_to?(:key?) && val.key?(attr_name) - validate_param!(attr_name, val) - end + validate_param!(attr_name, val) if @required || val.respond_to?(:key?) && val.key?(attr_name) rescue Grape::Exceptions::Validation => e array_errors << e end diff --git a/lib/grape/validations/validators/default.rb b/lib/grape/validations/validators/default.rb index ee620db46..79d6951f3 100644 --- a/lib/grape/validations/validators/default.rb +++ b/lib/grape/validations/validators/default.rb @@ -3,7 +3,7 @@ module Grape module Validations class DefaultValidator < Base - def initialize(attrs, options, required, scope, opts = {}) + def initialize(attrs, options, required, scope, **opts) @default = options super end @@ -21,9 +21,7 @@ def validate_param!(attr_name, params) def validate!(params) attrs = SingleAttributeIterator.new(self, @scope, params) attrs.each do |resource_params, attr_name| - if resource_params.is_a?(Hash) && resource_params[attr_name].nil? - validate_param!(attr_name, resource_params) - end + validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil? end end diff --git a/lib/grape/validations/validators/except_values.rb b/lib/grape/validations/validators/except_values.rb index 4ef94e97a..5ba1e306b 100644 --- a/lib/grape/validations/validators/except_values.rb +++ b/lib/grape/validations/validators/except_values.rb @@ -3,7 +3,7 @@ module Grape module Validations class ExceptValuesValidator < Base - def initialize(attrs, options, required, scope, opts = {}) + def initialize(attrs, options, required, scope, **opts) @except = options.is_a?(Hash) ? options[:value] : options super end diff --git a/lib/grape/validations/validators/values.rb b/lib/grape/validations/validators/values.rb index 7889f3bad..f3d676d0b 100644 --- a/lib/grape/validations/validators/values.rb +++ b/lib/grape/validations/validators/values.rb @@ -3,7 +3,7 @@ module Grape module Validations class ValuesValidator < Base - def initialize(attrs, options, required, scope, opts = {}) + def initialize(attrs, options, required, scope, **opts) if options.is_a?(Hash) @excepts = options[:except] @values = options[:value] diff --git a/spec/grape/middleware/error_spec.rb b/spec/grape/middleware/error_spec.rb index 66901292b..d586b9820 100644 --- a/spec/grape/middleware/error_spec.rb +++ b/spec/grape/middleware/error_spec.rb @@ -30,7 +30,7 @@ def app opts = options Rack::Builder.app do use Spec::Support::EndpointFaker - use Grape::Middleware::Error, opts + use Grape::Middleware::Error, **opts run ErrorSpec::ErrApp end end diff --git a/spec/grape/middleware/stack_spec.rb b/spec/grape/middleware/stack_spec.rb index e408ef9e8..3833337e4 100644 --- a/spec/grape/middleware/stack_spec.rb +++ b/spec/grape/middleware/stack_spec.rb @@ -34,7 +34,8 @@ def initialize(&block) expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 } .to change { subject.size }.by(1) expect(subject.last).to eq(StackSpec::BarMiddleware) - expect(subject.last.args).to eq([false, { my_arg: 42 }]) + expect(subject.last.args).to eq([false]) + expect(subject.last.opts).to eq(my_arg: 42) end it 'pushes a middleware class with block arguments onto the stack' do diff --git a/spec/support/versioned_helpers.rb b/spec/support/versioned_helpers.rb index f3055c6e5..8d7c07f18 100644 --- a/spec/support/versioned_helpers.rb +++ b/spec/support/versioned_helpers.rb @@ -6,7 +6,7 @@ module Support module Helpers # Returns the path with options[:version] prefixed if options[:using] is :path. # Returns normal path otherwise. - def versioned_path(options = {}) + def versioned_path(**options) case options[:using] when :path File.join('/', options[:prefix] || '', options[:version], options[:path]) @@ -43,13 +43,11 @@ def versioned_headers(**options) end end - def versioned_get(path, version_name, version_options = {}) + def versioned_get(path, version_name, **version_options) path = versioned_path(version_options.merge(version: version_name, path: path)) headers = versioned_headers(**version_options.merge(version: version_name)) params = {} - if version_options[:using] == :param - params = { version_options[:parameter] => version_name } - end + params = { version_options[:parameter] => version_name } if version_options[:using] == :param get path, params, headers end end From fe6a4a4741038f834e62d099c555dc040462b345 Mon Sep 17 00:00:00 2001 From: wanabe Date: Thu, 20 Aug 2020 21:30:08 +0900 Subject: [PATCH 242/290] Skip to set default value unless `meets_dependency?` (#2097) --- CHANGELOG.md | 1 + lib/grape/validations/validators/default.rb | 1 + .../validations/validators/default_spec.rb | 49 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5054c1dac..eb4988696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). * [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc). * [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim). +* [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe). ### 1.4.0 (2020/07/10) diff --git a/lib/grape/validations/validators/default.rb b/lib/grape/validations/validators/default.rb index 79d6951f3..dbf754ed8 100644 --- a/lib/grape/validations/validators/default.rb +++ b/lib/grape/validations/validators/default.rb @@ -21,6 +21,7 @@ def validate_param!(attr_name, params) def validate!(params) attrs = SingleAttributeIterator.new(self, @scope, params) attrs.each do |resource_params, attr_name| + next unless @scope.meets_dependency?(resource_params, params) validate_param!(attr_name, resource_params) if resource_params.is_a?(Hash) && resource_params[attr_name].nil? end end diff --git a/spec/grape/validations/validators/default_spec.rb b/spec/grape/validations/validators/default_spec.rb index 10220eb58..ae16445eb 100644 --- a/spec/grape/validations/validators/default_spec.rb +++ b/spec/grape/validations/validators/default_spec.rb @@ -419,4 +419,53 @@ def app end end end + + context 'array with default values and given conditions' do + subject do + Class.new(Grape::API) do + default_format :json + end + end + + def app + subject + end + + it 'applies the default values only if the conditions are met' do + subject.params do + requires :ary, type: Array do + requires :has_value, type: Grape::API::Boolean + given has_value: ->(has_value) { has_value } do + optional :type, type: String, values: %w[str int], default: 'str' + given type: ->(type) { type == 'str' } do + optional :str, type: String, default: 'a' + end + given type: ->(type) { type == 'int' } do + optional :int, type: Integer, default: 1 + end + end + end + end + subject.post('/nested_given_and_default') { declared(self.params) } + + params = { + ary: [ + { has_value: false }, + { has_value: true, type: 'int', int: 123 }, + { has_value: true, type: 'str', str: 'b' } + ] + } + expected = { + 'ary' => [ + { 'has_value' => false, 'type' => nil, 'int' => nil, 'str' => nil }, + { 'has_value' => true, 'type' => 'int', 'int' => 123, 'str' => nil }, + { 'has_value' => true, 'type' => 'str', 'int' => nil, 'str' => 'b' } + ] + } + + post '/nested_given_and_default', params + expect(last_response.status).to eq(201) + expect(JSON.parse(last_response.body)).to eq(expected) + end + end end From 192a2a2bbe59da0dac4ea435916ea5576d866155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gw=C3=A9na=C3=ABl=20Rault?= Date: Fri, 21 Aug 2020 15:10:44 +0200 Subject: [PATCH 243/290] Fixes redundant dependency check and adds a vrp benchmark (#2096) --- CHANGELOG.md | 1 + benchmark/large_model.rb | 245 ++++++++++++++++++++ benchmark/resource/vrp_example.json | 1 + lib/grape/validations/params_scope.rb | 3 +- spec/grape/validations/params_scope_spec.rb | 26 +++ 5 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 benchmark/large_model.rb create mode 100644 benchmark/resource/vrp_example.json diff --git a/CHANGELOG.md b/CHANGELOG.md index eb4988696..445342c0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc). * [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim). * [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe). +* [#2096](https://github.com/ruby-grape/grape/pull/2096): Fix redundant dependency check - [@braktar](https://github.com/braktar). ### 1.4.0 (2020/07/10) diff --git a/benchmark/large_model.rb b/benchmark/large_model.rb new file mode 100644 index 000000000..523e8887b --- /dev/null +++ b/benchmark/large_model.rb @@ -0,0 +1,245 @@ +# frozen_string_literal: true + +# gem 'grape', '=1.0.1' + +require 'grape' +require 'ruby-prof' +require 'hashie' + +class API < Grape::API + # include Grape::Extensions::Hash::ParamBuilder + # include Grape::Extensions::Hashie::Mash::ParamBuilder + + rescue_from do |e| + warn "\n\n#{e.class} (#{e.message}):\n " + e.backtrace.join("\n ") + "\n\n" + end + + prefix :api + version 'v1', using: :path + content_type :json, 'application/json; charset=UTF-8' + default_format :json + + def self.vrp_request_timewindow(this) + this.optional(:id, types: String) + this.optional(:start, types: [String, Float, Integer]) + this.optional(:end, types: [String, Float, Integer]) + this.optional(:day_index, type: Integer, values: 0..6) + this.at_least_one_of :start, :end, :day_index + end + + def self.vrp_request_indice_range(this) + this.optional(:start, type: Integer) + this.optional(:end, type: Integer) + end + + def self.vrp_request_point(this) + this.requires(:id, type: String, allow_blank: false) + this.optional(:location, type: Hash, allow_blank: false) do + requires(:lat, type: Float, allow_blank: false) + requires(:lon, type: Float, allow_blank: false) + end + end + + def self.vrp_request_unit(this) + this.requires(:id, type: String, allow_blank: false) + this.optional(:label, type: String) + this.optional(:counting, type: Boolean) + end + + def self.vrp_request_activity(this) + this.optional(:duration, types: [String, Float, Integer]) + this.optional(:additional_value, type: Integer) + this.optional(:setup_duration, types: [String, Float, Integer]) + this.optional(:late_multiplier, type: Float) + this.optional(:timewindow_start_day_shift_number, documentation: { hidden: true }, type: Integer) + this.requires(:point_id, type: String, allow_blank: false) + this.optional(:timewindows, type: Array) do + API.vrp_request_timewindow(self) + end + end + + def self.vrp_request_quantity(this) + this.optional(:id, type: String) + this.requires(:unit_id, type: String, allow_blank: false) + this.optional(:value, type: Float) + end + + def self.vrp_request_capacity(this) + this.optional(:id, type: String) + this.requires(:unit_id, type: String, allow_blank: false) + this.requires(:limit, type: Float, allow_blank: false) + this.optional(:initial, type: Float) + this.optional(:overload_multiplier, type: Float) + end + + def self.vrp_request_vehicle(this) + this.requires(:id, type: String, allow_blank: false) + this.optional(:cost_fixed, type: Float) + this.optional(:cost_distance_multiplier, type: Float) + this.optional(:cost_time_multiplier, type: Float) + + this.optional :router_dimension, type: String, values: %w[time distance] + this.optional(:skills, type: Array[Array[String]]) + + this.optional(:unavailable_work_day_indices, type: Array[Integer]) + + this.optional(:free_approach, type: Boolean) + this.optional(:free_return, type: Boolean) + + this.optional(:start_point_id, type: String) + this.optional(:end_point_id, type: String) + this.optional(:capacities, type: Array) do + API.vrp_request_capacity(self) + end + + this.optional(:sequence_timewindows, type: Array) do + API.vrp_request_timewindow(self) + end + end + + def self.vrp_request_service(this) + this.requires(:id, type: String, allow_blank: false) + this.optional(:priority, type: Integer, values: 0..8) + this.optional(:exclusion_cost, type: Integer) + + this.optional(:visits_number, type: Integer, coerce_with: ->(val) { val.to_i.positive? && val.to_i }, default: 1, allow_blank: false) + + this.optional(:unavailable_visit_indices, type: Array[Integer]) + this.optional(:unavailable_visit_day_indices, type: Array[Integer]) + + this.optional(:minimum_lapse, type: Float) + this.optional(:maximum_lapse, type: Float) + + this.optional(:sticky_vehicle_ids, type: Array[String]) + this.optional(:skills, type: Array[String]) + + this.optional(:type, type: Symbol) + this.optional(:activity, type: Hash) do + API.vrp_request_activity(self) + end + this.optional(:quantities, type: Array) do + API.vrp_request_quantity(self) + end + end + + def self.vrp_request_configuration(this) + this.optional(:preprocessing, type: Hash) do + API.vrp_request_preprocessing(self) + end + this.optional(:resolution, type: Hash) do + API.vrp_request_resolution(self) + end + this.optional(:restitution, type: Hash) do + API.vrp_request_restitution(self) + end + this.optional(:schedule, type: Hash) do + API.vrp_request_schedule(self) + end + end + + def self.vrp_request_partition(this) + this.requires(:method, type: String, values: %w[hierarchical_tree balanced_kmeans]) + this.optional(:metric, type: Symbol) + this.optional(:entity, type: Symbol, values: %i[vehicle work_day], coerce_with: ->(value) { value.to_sym }) + this.optional(:threshold, type: Integer) + end + + def self.vrp_request_preprocessing(this) + this.optional(:max_split_size, type: Integer) + this.optional(:partition_method, type: String, documentation: { hidden: true }) + this.optional(:partition_metric, type: Symbol, documentation: { hidden: true }) + this.optional(:kmeans_centroids, type: Array[Integer]) + this.optional(:cluster_threshold, type: Float) + this.optional(:force_cluster, type: Boolean) + this.optional(:prefer_short_segment, type: Boolean) + this.optional(:neighbourhood_size, type: Integer) + this.optional(:partitions, type: Array) do + API.vrp_request_partition(self) + end + this.optional(:first_solution_strategy, type: Array[String]) + end + + def self.vrp_request_resolution(this) + this.optional(:duration, type: Integer, allow_blank: false) + this.optional(:iterations, type: Integer, allow_blank: false) + this.optional(:iterations_without_improvment, type: Integer, allow_blank: false) + this.optional(:stable_iterations, type: Integer, allow_blank: false) + this.optional(:stable_coefficient, type: Float, allow_blank: false) + this.optional(:initial_time_out, type: Integer, allow_blank: false, documentation: { hidden: true }) + this.optional(:minimum_duration, type: Integer, allow_blank: false) + this.optional(:time_out_multiplier, type: Integer) + this.optional(:vehicle_limit, type: Integer) + this.optional(:solver_parameter, type: Integer, documentation: { hidden: true }) + this.optional(:solver, type: Boolean, default: true) + this.optional(:same_point_day, type: Boolean) + this.optional(:allow_partial_assignment, type: Boolean, default: true) + this.optional(:split_number, type: Integer) + this.optional(:evaluate_only, type: Boolean) + this.optional(:several_solutions, type: Integer, allow_blank: false, default: 1) + this.optional(:batch_heuristic, type: Boolean, default: false) + this.optional(:variation_ratio, type: Integer) + this.optional(:repetition, type: Integer, documentation: { hidden: true }) + this.at_least_one_of :duration, :iterations, :iterations_without_improvment, :stable_iterations, :stable_coefficient, :initial_time_out, :minimum_duration + this.mutually_exclusive :initial_time_out, :minimum_duration + end + + def self.vrp_request_restitution(this) + this.optional(:geometry, type: Boolean) + this.optional(:geometry_polyline, type: Boolean) + this.optional(:intermediate_solutions, type: Boolean) + this.optional(:csv, type: Boolean) + this.optional(:allow_empty_result, type: Boolean) + end + + def self.vrp_request_schedule(this) + this.optional(:range_indices, type: Hash) do + API.vrp_request_indice_range(self) + end + this.optional(:unavailable_indices, type: Array[Integer]) + end + + params do + optional(:vrp, type: Hash, documentation: { param_type: 'body' }) do + optional(:name, type: String) + + optional(:points, type: Array) do + API.vrp_request_point(self) + end + + optional(:units, type: Array) do + API.vrp_request_unit(self) + end + + requires(:vehicles, type: Array) do + API.vrp_request_vehicle(self) + end + + optional(:services, type: Array, allow_blank: false) do + API.vrp_request_service(self) + end + + optional(:configuration, type: Hash) do + API.vrp_request_configuration(self) + end + end + end + post '/' do + 'hello' + end +end +puts Grape::VERSION + +options = { + method: 'POST', + params: JSON.parse(File.read('benchmark/resource/vrp_example.json')) +} + +env = Rack::MockRequest.env_for('/api/v1', options) + +start = Time.now +result = RubyProf.profile do + API.call env +end +puts Time.now - start +printer = RubyProf::FlatPrinter.new(result) +File.open('test_prof.out', 'w+') { |f| printer.print(f, {}) } diff --git a/benchmark/resource/vrp_example.json b/benchmark/resource/vrp_example.json new file mode 100644 index 000000000..be3d734a3 --- /dev/null +++ b/benchmark/resource/vrp_example.json @@ -0,0 +1 @@ +{"vrp":{"points":[{"id":"1002100","location":{"lat":48.865,"lon":2.3054}},{"id":"1103548","location":{"lat":48.8711,"lon":2.3079}},{"id":"1142617","location":{"lat":48.8756,"lon":2.302}},{"id":"1147052","location":{"lat":48.8758,"lon":2.3074}},{"id":"1104396","location":{"lat":48.8776,"lon":2.3056}},{"id":"1139292","location":{"lat":48.8767,"lon":2.3032}},{"id":"1139149","location":{"lat":48.8767,"lon":2.3073}},{"id":"1118656","location":{"lat":48.8732,"lon":2.3049}},{"id":"1123712","location":{"lat":48.8755,"lon":2.3023}},{"id":"1120539","location":{"lat":48.8739,"lon":2.303}},{"id":"1109631","location":{"lat":48.8774,"lon":2.3047}},{"id":"1139151","location":{"lat":48.8767,"lon":2.3071}},{"id":"1005088","location":{"lat":48.8714,"lon":2.307}},{"id":"1054022","location":{"lat":48.8735,"lon":2.3095}},{"id":"1052132","location":{"lat":48.8733,"lon":2.3058}},{"id":"1080067","location":{"lat":48.8755,"lon":2.3024}},{"id":"1080537","location":{"lat":48.8732,"lon":2.3057}},{"id":"1001821","location":{"lat":48.8721,"lon":2.3043}},{"id":"1033652","location":{"lat":48.8758,"lon":2.3031}},{"id":"1127811","location":{"lat":48.8768,"lon":2.3091}},{"id":"1031446","location":{"lat":48.8723,"lon":2.3033}},{"id":"1004332","location":{"lat":48.8733,"lon":2.3056}},{"id":"1030348","location":{"lat":48.875,"lon":2.3051}},{"id":"1062118","location":{"lat":48.873,"lon":2.305}},{"id":"1035112","location":{"lat":48.8755,"lon":2.3023}},{"id":"1001140","location":{"lat":48.8776,"lon":2.3038}},{"id":"1144968","location":{"lat":48.8749,"lon":2.304}},{"id":"1136835","location":{"lat":48.8732,"lon":2.3051}},{"id":"1133790","location":{"lat":48.879,"lon":2.3043}},{"id":"1133878","location":{"lat":48.8785,"lon":2.3039}},{"id":"1007882","location":{"lat":48.8738,"lon":2.2965}},{"id":"1020596","location":{"lat":48.8664,"lon":2.31}},{"id":"1064282","location":{"lat":48.8731,"lon":2.3072}},{"id":"1134687","location":{"lat":48.8759,"lon":2.3077}},{"id":"1135600","location":{"lat":48.8768,"lon":2.3092}},{"id":"1133576","location":{"lat":48.8768,"lon":2.3091}},{"id":"1138821","location":{"lat":48.8749,"lon":2.3035}},{"id":"1066596","location":{"lat":48.8722,"lon":2.2967}},{"id":"1080091","location":{"lat":48.8787,"lon":2.3051}},{"id":"1094392","location":{"lat":48.8732,"lon":2.3131}},{"id":"1071805","location":{"lat":48.8755,"lon":2.3022}},{"id":"1064291","location":{"lat":48.8731,"lon":2.3072}},{"id":"1137046","location":{"lat":48.8732,"lon":2.3051}},{"id":"1131694","location":{"lat":48.8744,"lon":2.2984}},{"id":"1005035","location":{"lat":48.8786,"lon":2.3131}},{"id":"1004005","location":{"lat":48.8733,"lon":2.3062}},{"id":"1041519","location":{"lat":48.8755,"lon":2.3022}},{"id":"1148428","location":{"lat":0.0,"lon":0.0}},{"id":"1119178","location":{"lat":48.8726,"lon":2.304}},{"id":"1030515","location":{"lat":48.8789,"lon":2.303}},{"id":"1130633","location":{"lat":48.8755,"lon":2.3023}},{"id":"1132792","location":{"lat":48.8744,"lon":2.2984}},{"id":"1124356","location":{"lat":48.8753,"lon":2.3047}},{"id":"1121089","location":{"lat":48.8769,"lon":2.3074}},{"id":"1102925","location":{"lat":48.8732,"lon":2.3131}},{"id":"1102928","location":{"lat":48.8732,"lon":2.3131}},{"id":"1105871","location":{"lat":48.872,"lon":2.3039}},{"id":"1116088","location":{"lat":48.8768,"lon":2.3091}},{"id":"1109290","location":{"lat":48.8747,"lon":2.2982}},{"id":"1131649","location":{"lat":48.8775,"lon":2.2997}},{"id":"1136697","location":{"lat":48.8732,"lon":2.3051}},{"id":"1030517","location":{"lat":48.8751,"lon":2.3064}},{"id":"1132871","location":{"lat":48.8732,"lon":2.3051}},{"id":"1148306","location":{"lat":0.0,"lon":0.0}},{"id":"1126467","location":{"lat":48.8768,"lon":2.3091}},{"id":"1130723","location":{"lat":48.8768,"lon":2.3006}},{"id":"1099009","location":{"lat":48.874,"lon":2.2984}},{"id":"1095726","location":{"lat":48.8777,"lon":2.2994}},{"id":"1005056","location":{"lat":48.8776,"lon":2.3038}},{"id":"1122952","location":{"lat":48.8738,"lon":2.3005}},{"id":"1126324","location":{"lat":48.8768,"lon":2.3091}},{"id":"1124513","location":{"lat":48.8732,"lon":2.3051}},{"id":"1124103","location":{"lat":48.873,"lon":2.3047}},{"id":"1131394","location":{"lat":48.8747,"lon":2.3239}},{"id":"1133951","location":{"lat":48.8704,"lon":2.3211}},{"id":"1137715","location":{"lat":48.8698,"lon":2.3182}},{"id":"1132589","location":{"lat":48.8739,"lon":2.3214}},{"id":"1145751","location":{"lat":48.8715,"lon":2.3236}},{"id":"1070749","location":{"lat":48.8712,"lon":2.3194}},{"id":"1070735","location":{"lat":48.8703,"lon":2.3176}},{"id":"1002504","location":{"lat":48.8696,"lon":2.3188}},{"id":"1007287","location":{"lat":48.8707,"lon":2.3199}},{"id":"1005919","location":{"lat":48.8698,"lon":2.3178}},{"id":"1143914","location":{"lat":48.8693,"lon":2.3201}},{"id":"1144594","location":{"lat":48.8764,"lon":2.3083}},{"id":"1127546","location":{"lat":48.8692,"lon":2.3209}},{"id":"1123348","location":{"lat":48.8742,"lon":2.3171}},{"id":"1103574","location":{"lat":48.8711,"lon":2.3185}},{"id":"1087334","location":{"lat":48.8724,"lon":2.3183}},{"id":"1088315","location":{"lat":48.8762,"lon":2.3135}},{"id":"1054230","location":{"lat":48.8697,"lon":2.3198}},{"id":"1058540","location":{"lat":48.8701,"lon":2.3209}},{"id":"1106440","location":{"lat":48.87,"lon":2.3185}},{"id":"1120609","location":{"lat":48.8729,"lon":2.3228}},{"id":"1119750","location":{"lat":48.8693,"lon":2.3195}},{"id":"1107065","location":{"lat":48.8708,"lon":2.3202}},{"id":"1096970","location":{"lat":48.8733,"lon":2.3193}},{"id":"1124357","location":{"lat":48.8716,"lon":2.3216}},{"id":"1130453","location":{"lat":48.8763,"lon":2.3139}},{"id":"1121283","location":{"lat":48.8733,"lon":2.3213}},{"id":"1143992","location":{"lat":48.8713,"lon":2.3226}},{"id":"1020782","location":{"lat":48.8717,"lon":2.3198}},{"id":"1109136","location":{"lat":48.8732,"lon":2.3214}},{"id":"1107406","location":{"lat":48.87,"lon":2.3189}},{"id":"1001454","location":{"lat":48.8717,"lon":2.322}},{"id":"1031405","location":{"lat":48.8733,"lon":2.3181}},{"id":"1099019","location":{"lat":48.8712,"lon":2.3184}},{"id":"1040631","location":{"lat":48.8722,"lon":2.3231}},{"id":"1030463","location":{"lat":48.8725,"lon":2.3218}},{"id":"1033191","location":{"lat":48.8736,"lon":2.3213}},{"id":"1133959","location":{"lat":48.873,"lon":2.3163}},{"id":"1004770","location":{"lat":48.8788,"lon":2.3171}},{"id":"1129651","location":{"lat":48.8713,"lon":2.3226}},{"id":"1121101","location":{"lat":48.8701,"lon":2.3183}},{"id":"1119751","location":{"lat":48.8703,"lon":2.3212}},{"id":"1137030","location":{"lat":48.8729,"lon":2.3223}},{"id":"1134263","location":{"lat":48.8764,"lon":2.3142}},{"id":"1133530","location":{"lat":48.873,"lon":2.3176}},{"id":"1142237","location":{"lat":48.8713,"lon":2.3226}},{"id":"1030487","location":{"lat":48.8701,"lon":2.3191}},{"id":"1004647","location":{"lat":48.874,"lon":2.3186}},{"id":"1004716","location":{"lat":48.8737,"lon":2.3172}},{"id":"1144936","location":{"lat":48.8772,"lon":2.3165}},{"id":"1134666","location":{"lat":48.874,"lon":2.3184}},{"id":"1006725","location":{"lat":48.8736,"lon":2.3158}},{"id":"1092502","location":{"lat":48.8754,"lon":2.323}},{"id":"1008001","location":{"lat":48.8749,"lon":2.3158}},{"id":"1144493","location":{"lat":48.873,"lon":2.3124}},{"id":"1147114","location":{"lat":48.8738,"lon":2.3165}},{"id":"1147721","location":{"lat":0.0,"lon":0.0}},{"id":"1003152","location":{"lat":48.8763,"lon":2.3205}},{"id":"1110450","location":{"lat":48.8735,"lon":2.3142}},{"id":"1070260","location":{"lat":48.8742,"lon":2.3206}},{"id":"1132451","location":{"lat":48.8739,"lon":2.3193}},{"id":"1122595","location":{"lat":48.8743,"lon":2.3212}},{"id":"1134348","location":{"lat":48.8749,"lon":2.3211}},{"id":"1127201","location":{"lat":48.8732,"lon":2.3131}},{"id":"1138580","location":{"lat":48.8751,"lon":2.3211}},{"id":"1143039","location":{"lat":48.8731,"lon":2.3135}},{"id":"1132224","location":{"lat":48.8746,"lon":2.3226}},{"id":"1095177","location":{"lat":48.877,"lon":2.3175}},{"id":"1111407","location":{"lat":48.8745,"lon":2.3219}},{"id":"1117925","location":{"lat":48.8739,"lon":2.3178}},{"id":"1135294","location":{"lat":48.8737,"lon":2.3138}},{"id":"1031534","location":{"lat":48.8735,"lon":2.3143}},{"id":"1047944","location":{"lat":48.8739,"lon":2.3195}},{"id":"1050281","location":{"lat":48.873,"lon":2.3157}},{"id":"1054024","location":{"lat":48.8754,"lon":2.3236}},{"id":"1040973","location":{"lat":48.8765,"lon":2.3173}},{"id":"1063338","location":{"lat":48.8752,"lon":2.3171}},{"id":"1031918","location":{"lat":48.8739,"lon":2.3178}},{"id":"1145151","location":{"lat":48.8739,"lon":2.3193}},{"id":"1054036","location":{"lat":48.8748,"lon":2.3215}},{"id":"1004708","location":{"lat":48.875,"lon":2.3203}},{"id":"1002561","location":{"lat":48.8744,"lon":2.3174}},{"id":"1005880","location":{"lat":48.8738,"lon":2.3161}},{"id":"1144485","location":{"lat":48.8736,"lon":2.3139}},{"id":"1116199","location":{"lat":48.8737,"lon":2.3142}},{"id":"1123435","location":{"lat":48.8738,"lon":2.318}},{"id":"1124213","location":{"lat":48.8743,"lon":2.3182}},{"id":"startvehicule1","location":{"lat":48.78,"lon":2.43}},{"id":"startvehicule2","location":{"lat":48.78,"lon":2.43}},{"id":"endvehicule1","location":{"lat":48.78,"lon":2.43}},{"id":"endvehicule2","location":{"lat":48.78,"lon":2.43}}],"units":[{"id":"kg","label":"kg"},{"id":"l","label":"l"},{"id":"qte","label":"qte"}],"services":[{"id":"1002100_EMP_ 28_1FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":3,"minimum_lapse":120.0,"activity":{"point_id":"1002100","duration":120,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":30600,"end":45000,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1147052_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_SAV_ 84_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147052_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139149_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1104396_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109631_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139151_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1005088_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1052132_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1052132_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080537_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1033652_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1052132_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1004332_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1001821_ASC_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1139149_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1062118_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1062118_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1062118_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1062118_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1035112_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1035112_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1035112_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1144968_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144968_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144968_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136835_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1134687_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1135600_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":49.35},{"unit_id":"l","value":601.6},{"unit_id":"qte","value":47.0}],"visits_number":3,"minimum_lapse":376.0,"activity":{"point_id":"1135600","duration":376,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135600_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":49.35},{"unit_id":"l","value":601.6},{"unit_id":"qte","value":47.0}],"visits_number":3,"minimum_lapse":376.0,"activity":{"point_id":"1135600","duration":376,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133576_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1133576_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1133576_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1138821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138821_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134687_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139149_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1134687_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1080091_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080091_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1094392_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1094392","duration":90,"setup_duration":120,"timewindows":[{"start":25200,"end":45000,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":25200,"end":45000,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":25200,"end":45000,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":25200,"end":45000,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":25200,"end":45000,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080091_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1134687_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1136835_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1137046_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137046_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137046_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1066596_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004005_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1041519_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.59},{"unit_id":"l","value":2.625},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1041519","duration":28,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1131694_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1131694_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_TAP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_DIF_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1130633_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1130633_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130633_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102925_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102928_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1116088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1131649_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1136697_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030515_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030517_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030517_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132871_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1132871","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148306_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030517_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136835_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1126467_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1130633_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102928_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1130633_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1136697_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1130633_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1136697_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132871_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1132871","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1066596_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1066596_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1004005_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1116088_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1030517_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148306_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030517_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1041519_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.59},{"unit_id":"l","value":2.625},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1041519","duration":28,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1004005_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139151_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139151_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1005088_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1139149_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1147052_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147052_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109631_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1004332_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1080537_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102925_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102925_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102928_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1116088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080537_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1052132_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004332_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1033652_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1052132_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1052132_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1130723_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":115.2},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1130723","duration":72,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1099009_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_CLI_168_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005056_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1005056","duration":26,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005056_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1005056","duration":26,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1080067_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1095726_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122952_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1126324_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1126324_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1126324_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1126467_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1126467_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1130723_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":115.2},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1130723","duration":72,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1126324_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1030348_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1124513_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1122952_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124103_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124103_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122952_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1124103_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124513_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1004005_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1137046_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1131694_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137046_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137046_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1007882_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030515_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004005_TAP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142617_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031446_ASC_ 84_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1139292_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1142617_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148428_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_DIF_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1131394_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1131394_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137715_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1007287_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":15.5},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":50.0}],"visits_number":3,"minimum_lapse":200.0,"activity":{"point_id":"1007287","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127546_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123348_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103574_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1087334_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1058540_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1058540","duration":180,"setup_duration":120,"timewindows":[{"start":30600,"end":72000,"day_index":0},{"start":30600,"end":72000,"day_index":1},{"start":30600,"end":72000,"day_index":2},{"start":30600,"end":72000,"day_index":3},{"start":30600,"end":72000,"day_index":4}]},"type":"service"},{"id":"1054230_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1103574_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1120609_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123348_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1120609_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1070749_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1121283_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1143992_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1143992_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1121283_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1001454_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1070735_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031405_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031405_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1070749_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030463_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1033191_ASC_ 84_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":400.0,"activity":{"point_id":"1033191","duration":400,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1040631_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1054230_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1133951_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1133959_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_ASC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1129651_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1129651_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121101_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004770_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1119751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121101_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119751_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054230_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1137030_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137030_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1133530_PCP_ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137030_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1142237_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142237_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1133530_PH _ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133530_SAV_ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1005919_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1137030_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1142237_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142237_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_CLI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_INI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1030487_SNC_ 42_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":2,"minimum_lapse":180.0,"activity":{"point_id":"1030487","duration":180,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1007287_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":15.5},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":50.0}],"visits_number":3,"minimum_lapse":200.0,"activity":{"point_id":"1007287","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1096970_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1031405_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031405_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1070735_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131394_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1123348_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1120609_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1123348_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127546_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1131394_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1127546_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030348_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1143992_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1143992_SAV_ 84_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1020782_CLI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_INI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1001454_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1103574_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103574_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_INI_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1005919_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_ASC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1144594_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1121101_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119751_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1096970_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1121101_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1129651_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1133951_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1129651_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121101_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1142237_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1058540_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1058540","duration":180,"setup_duration":120,"timewindows":[{"start":30600,"end":72000,"day_index":0},{"start":30600,"end":72000,"day_index":1},{"start":30600,"end":72000,"day_index":2},{"start":30600,"end":72000,"day_index":3},{"start":30600,"end":72000,"day_index":4}]},"type":"service"},{"id":"1109631_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1142237_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137030_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1137030_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1142237_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137030_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137030_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1132589_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1142237_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120539_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_DIF_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_SNC_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004716_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1004716","duration":104,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":30600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1144936_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144936_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144936_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1006725_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1006725_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1092502_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_CLI_ 84_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_TAP_ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1006725_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1008001_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1144936_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147114_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.94},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1147114","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147114_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":2.94},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1147114","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147721_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1003152_SNC_ 42_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_BOB_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_PH _ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122595_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1122595_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1122595_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1110450_SAV_ 14_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1134348_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127201_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":14.28},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1127201","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127201_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":14.28},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1127201","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143039_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":26.56},{"unit_id":"l","value":105.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":800.0,"activity":{"point_id":"1143039","duration":800,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132224_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1132224_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1110450_PH _ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095177_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":51.2},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1095177","duration":32,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1111407_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":37.5},{"unit_id":"l","value":145.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1111407","duration":500,"setup_duration":120,"timewindows":[{"start":25200,"end":50400,"day_index":0},{"start":25200,"end":50400,"day_index":1},{"start":25200,"end":50400,"day_index":2},{"start":25200,"end":50400,"day_index":3},{"start":25200,"end":50400,"day_index":4}]},"type":"service"},{"id":"1117925_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132224_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1138580_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_CLI_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_PH _ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1031534_SAV_ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1047944_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":31.0,"activity":{"point_id":"1047944","duration":31,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1047944_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":31.0,"activity":{"point_id":"1047944","duration":31,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1050281_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":52.0,"activity":{"point_id":"1050281","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054024_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":102.0},{"unit_id":"qte","value":3.0}],"visits_number":12,"minimum_lapse":270.0,"activity":{"point_id":"1054024","duration":270,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1050281_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":52.0,"activity":{"point_id":"1050281","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1040973_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":6,"minimum_lapse":186.0,"activity":{"point_id":"1040973","duration":186,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1063338_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1063338","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031534_CLI_ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1070260_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1031918_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1135294_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_EMP_ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054036_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":180.0,"activity":{"point_id":"1054036","duration":180,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1003152_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_ASC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1110450_BOB_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004708_LPL_ 28_3FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":182.0,"activity":{"point_id":"1004708","duration":182,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004708_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":182.0,"activity":{"point_id":"1004708","duration":182,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1145151_EMP_ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054036_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":180.0,"activity":{"point_id":"1054036","duration":180,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002561_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002561_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005880_EMP_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005880_SAV_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002561_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_LPL_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116199_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":18.46},{"unit_id":"l","value":42.0},{"unit_id":"qte","value":76.0}],"visits_number":3,"minimum_lapse":304.0,"activity":{"point_id":"1116199","duration":304,"setup_duration":120,"timewindows":[{"start":33300,"end":61200,"day_index":0},{"start":33300,"end":61200,"day_index":1},{"start":33300,"end":61200,"day_index":2},{"start":33300,"end":61200,"day_index":3},{"start":33300,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123435_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123435","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_CLI_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143039_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":26.56},{"unit_id":"l","value":105.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":800.0,"activity":{"point_id":"1143039","duration":800,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005880_PH _ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005880_SNC_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031918_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1031918_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1004647_SNC_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"}],"vehicles":[{"id":"vehicule1","start_point_id":"startvehicule1","end_point_id":"endvehicule1","router_mode":"car","speed_multiplier":0.75,"cost_time_multiplier":1.0,"router_dimension":"time","sequence_timewindows":[{"start":21600,"end":45000,"day_index":0},{"start":21600,"end":45000,"day_index":1},{"start":21600,"end":45000,"day_index":2},{"start":21600,"end":45000,"day_index":3},{"start":21600,"end":45000,"day_index":4}],"capacities":[{"unit_id":"kg","limit":850.0},{"unit_id":"l","limit":7435.0},{"unit_id":"qte","limit":9999.0}],"unavailable_work_day_indices":[5,6],"traffic":true,"track":true,"motorway":true,"toll":true,"max_walk_distance":750,"approach":"unrestricted"},{"id":"vehicule2","start_point_id":"startvehicule2","end_point_id":"endvehicule2","router_mode":"car","speed_multiplier":0.75,"cost_time_multiplier":1.0,"router_dimension":"time","sequence_timewindows":[{"start":21600,"end":45000,"day_index":0},{"start":21600,"end":45000,"day_index":1},{"start":21600,"end":45000,"day_index":2},{"start":21600,"end":45000,"day_index":3},{"start":21600,"end":45000,"day_index":4}],"capacities":[{"unit_id":"kg","limit":1210.0},{"unit_id":"l","limit":6254.0},{"unit_id":"qte","limit":9999.0}],"unavailable_work_day_indices":[5,6],"traffic":true,"track":true,"motorway":true,"toll":true,"max_walk_distance":750,"approach":"unrestricted"}],"configuration":{"preprocessing":{"use_periodic_heuristic":true,"prefer_short_segment":true,"partition_method":"balanced_kmeans","partition_metric":"duration"},"resolution":{"same_point_day":true,"solver_parameter":-1,"duration":225000,"initial_time_out":112500,"time_out_multiplier":2},"schedule":{"range_indices":{"start":0,"end":83}},"restitution":{"csv":true,"intermediate_solutions":false}}}} \ No newline at end of file diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 84e72f8bc..a51be6e1e 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -54,11 +54,12 @@ def should_validate?(parameters) end def meets_dependency?(params, request_params) + return true unless @dependent_on + if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params) return false end - return true unless @dependent_on return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array) return false unless params.respond_to?(:with_indifferent_access) params = params.with_indifferent_access diff --git a/spec/grape/validations/params_scope_spec.rb b/spec/grape/validations/params_scope_spec.rb index 984524c8f..9ef14af9f 100644 --- a/spec/grape/validations/params_scope_spec.rb +++ b/spec/grape/validations/params_scope_spec.rb @@ -633,6 +633,32 @@ def initialize(value) expect(last_response.status).to eq(200) end + it 'detect unmet nested dependency' do + subject.params do + requires :a, type: String, allow_blank: false, values: %w[x y z] + given a: ->(val) { val == 'z' } do + requires :inner3, type: Array, allow_blank: false do + requires :bar, type: String, allow_blank: false + given bar: ->(val) { val == 'b' } do + requires :baz, type: Array do + optional :baz_category, type: String + end + end + given bar: ->(val) { val == 'c' } do + requires :baz, type: Array do + requires :baz_category, type: String + end + end + end + end + end + subject.get('/nested-dependency') { declared(params).to_json } + + get '/nested-dependency', a: 'z', inner3: [{ bar: 'c', baz: [{ unrelated: 'nope' }] }] + expect(last_response.status).to eq(400) + expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing' + end + it 'includes the parameter within #declared(params)' do get '/test', a: true, b: true From 7e95ac8ce98f7b049ab216d6c449a211150edc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gw=C3=A9na=C3=ABl=20Rault?= Date: Thu, 27 Aug 2020 15:07:17 +0200 Subject: [PATCH 244/290] Add nested array coercion spec (#2098) --- CHANGELOG.md | 1 + benchmark/large_model.rb | 10 +++++--- benchmark/resource/vrp_example.json | 2 +- .../validations/types/custom_type_coercer.rb | 14 ++++++++++- .../validations/validators/coerce_spec.rb | 24 +++++++++++++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 445342c0d..609e4235a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim). * [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe). * [#2096](https://github.com/ruby-grape/grape/pull/2096): Fix redundant dependency check - [@braktar](https://github.com/braktar). +* [#2096](https://github.com/ruby-grape/grape/pull/2098): Fix nested coercion - [@braktar](https://github.com/braktar). ### 1.4.0 (2020/07/10) diff --git a/benchmark/large_model.rb b/benchmark/large_model.rb index 523e8887b..48827528d 100644 --- a/benchmark/large_model.rb +++ b/benchmark/large_model.rb @@ -79,7 +79,7 @@ def self.vrp_request_vehicle(this) this.optional(:cost_time_multiplier, type: Float) this.optional :router_dimension, type: String, values: %w[time distance] - this.optional(:skills, type: Array[Array[String]]) + this.optional(:skills, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val }) this.optional(:unavailable_work_day_indices, type: Array[Integer]) @@ -224,7 +224,10 @@ def self.vrp_request_schedule(this) end end post '/' do - 'hello' + { + skills_v1: params[:vrp][:vehicles].first[:skills], + skills_v2: params[:vrp][:vehicles].last[:skills] + } end end puts Grape::VERSION @@ -238,7 +241,8 @@ def self.vrp_request_schedule(this) start = Time.now result = RubyProf.profile do - API.call env + response = API.call env + puts response.last end puts Time.now - start printer = RubyProf::FlatPrinter.new(result) diff --git a/benchmark/resource/vrp_example.json b/benchmark/resource/vrp_example.json index be3d734a3..d9c9cd5bc 100644 --- a/benchmark/resource/vrp_example.json +++ b/benchmark/resource/vrp_example.json @@ -1 +1 @@ -{"vrp":{"points":[{"id":"1002100","location":{"lat":48.865,"lon":2.3054}},{"id":"1103548","location":{"lat":48.8711,"lon":2.3079}},{"id":"1142617","location":{"lat":48.8756,"lon":2.302}},{"id":"1147052","location":{"lat":48.8758,"lon":2.3074}},{"id":"1104396","location":{"lat":48.8776,"lon":2.3056}},{"id":"1139292","location":{"lat":48.8767,"lon":2.3032}},{"id":"1139149","location":{"lat":48.8767,"lon":2.3073}},{"id":"1118656","location":{"lat":48.8732,"lon":2.3049}},{"id":"1123712","location":{"lat":48.8755,"lon":2.3023}},{"id":"1120539","location":{"lat":48.8739,"lon":2.303}},{"id":"1109631","location":{"lat":48.8774,"lon":2.3047}},{"id":"1139151","location":{"lat":48.8767,"lon":2.3071}},{"id":"1005088","location":{"lat":48.8714,"lon":2.307}},{"id":"1054022","location":{"lat":48.8735,"lon":2.3095}},{"id":"1052132","location":{"lat":48.8733,"lon":2.3058}},{"id":"1080067","location":{"lat":48.8755,"lon":2.3024}},{"id":"1080537","location":{"lat":48.8732,"lon":2.3057}},{"id":"1001821","location":{"lat":48.8721,"lon":2.3043}},{"id":"1033652","location":{"lat":48.8758,"lon":2.3031}},{"id":"1127811","location":{"lat":48.8768,"lon":2.3091}},{"id":"1031446","location":{"lat":48.8723,"lon":2.3033}},{"id":"1004332","location":{"lat":48.8733,"lon":2.3056}},{"id":"1030348","location":{"lat":48.875,"lon":2.3051}},{"id":"1062118","location":{"lat":48.873,"lon":2.305}},{"id":"1035112","location":{"lat":48.8755,"lon":2.3023}},{"id":"1001140","location":{"lat":48.8776,"lon":2.3038}},{"id":"1144968","location":{"lat":48.8749,"lon":2.304}},{"id":"1136835","location":{"lat":48.8732,"lon":2.3051}},{"id":"1133790","location":{"lat":48.879,"lon":2.3043}},{"id":"1133878","location":{"lat":48.8785,"lon":2.3039}},{"id":"1007882","location":{"lat":48.8738,"lon":2.2965}},{"id":"1020596","location":{"lat":48.8664,"lon":2.31}},{"id":"1064282","location":{"lat":48.8731,"lon":2.3072}},{"id":"1134687","location":{"lat":48.8759,"lon":2.3077}},{"id":"1135600","location":{"lat":48.8768,"lon":2.3092}},{"id":"1133576","location":{"lat":48.8768,"lon":2.3091}},{"id":"1138821","location":{"lat":48.8749,"lon":2.3035}},{"id":"1066596","location":{"lat":48.8722,"lon":2.2967}},{"id":"1080091","location":{"lat":48.8787,"lon":2.3051}},{"id":"1094392","location":{"lat":48.8732,"lon":2.3131}},{"id":"1071805","location":{"lat":48.8755,"lon":2.3022}},{"id":"1064291","location":{"lat":48.8731,"lon":2.3072}},{"id":"1137046","location":{"lat":48.8732,"lon":2.3051}},{"id":"1131694","location":{"lat":48.8744,"lon":2.2984}},{"id":"1005035","location":{"lat":48.8786,"lon":2.3131}},{"id":"1004005","location":{"lat":48.8733,"lon":2.3062}},{"id":"1041519","location":{"lat":48.8755,"lon":2.3022}},{"id":"1148428","location":{"lat":0.0,"lon":0.0}},{"id":"1119178","location":{"lat":48.8726,"lon":2.304}},{"id":"1030515","location":{"lat":48.8789,"lon":2.303}},{"id":"1130633","location":{"lat":48.8755,"lon":2.3023}},{"id":"1132792","location":{"lat":48.8744,"lon":2.2984}},{"id":"1124356","location":{"lat":48.8753,"lon":2.3047}},{"id":"1121089","location":{"lat":48.8769,"lon":2.3074}},{"id":"1102925","location":{"lat":48.8732,"lon":2.3131}},{"id":"1102928","location":{"lat":48.8732,"lon":2.3131}},{"id":"1105871","location":{"lat":48.872,"lon":2.3039}},{"id":"1116088","location":{"lat":48.8768,"lon":2.3091}},{"id":"1109290","location":{"lat":48.8747,"lon":2.2982}},{"id":"1131649","location":{"lat":48.8775,"lon":2.2997}},{"id":"1136697","location":{"lat":48.8732,"lon":2.3051}},{"id":"1030517","location":{"lat":48.8751,"lon":2.3064}},{"id":"1132871","location":{"lat":48.8732,"lon":2.3051}},{"id":"1148306","location":{"lat":0.0,"lon":0.0}},{"id":"1126467","location":{"lat":48.8768,"lon":2.3091}},{"id":"1130723","location":{"lat":48.8768,"lon":2.3006}},{"id":"1099009","location":{"lat":48.874,"lon":2.2984}},{"id":"1095726","location":{"lat":48.8777,"lon":2.2994}},{"id":"1005056","location":{"lat":48.8776,"lon":2.3038}},{"id":"1122952","location":{"lat":48.8738,"lon":2.3005}},{"id":"1126324","location":{"lat":48.8768,"lon":2.3091}},{"id":"1124513","location":{"lat":48.8732,"lon":2.3051}},{"id":"1124103","location":{"lat":48.873,"lon":2.3047}},{"id":"1131394","location":{"lat":48.8747,"lon":2.3239}},{"id":"1133951","location":{"lat":48.8704,"lon":2.3211}},{"id":"1137715","location":{"lat":48.8698,"lon":2.3182}},{"id":"1132589","location":{"lat":48.8739,"lon":2.3214}},{"id":"1145751","location":{"lat":48.8715,"lon":2.3236}},{"id":"1070749","location":{"lat":48.8712,"lon":2.3194}},{"id":"1070735","location":{"lat":48.8703,"lon":2.3176}},{"id":"1002504","location":{"lat":48.8696,"lon":2.3188}},{"id":"1007287","location":{"lat":48.8707,"lon":2.3199}},{"id":"1005919","location":{"lat":48.8698,"lon":2.3178}},{"id":"1143914","location":{"lat":48.8693,"lon":2.3201}},{"id":"1144594","location":{"lat":48.8764,"lon":2.3083}},{"id":"1127546","location":{"lat":48.8692,"lon":2.3209}},{"id":"1123348","location":{"lat":48.8742,"lon":2.3171}},{"id":"1103574","location":{"lat":48.8711,"lon":2.3185}},{"id":"1087334","location":{"lat":48.8724,"lon":2.3183}},{"id":"1088315","location":{"lat":48.8762,"lon":2.3135}},{"id":"1054230","location":{"lat":48.8697,"lon":2.3198}},{"id":"1058540","location":{"lat":48.8701,"lon":2.3209}},{"id":"1106440","location":{"lat":48.87,"lon":2.3185}},{"id":"1120609","location":{"lat":48.8729,"lon":2.3228}},{"id":"1119750","location":{"lat":48.8693,"lon":2.3195}},{"id":"1107065","location":{"lat":48.8708,"lon":2.3202}},{"id":"1096970","location":{"lat":48.8733,"lon":2.3193}},{"id":"1124357","location":{"lat":48.8716,"lon":2.3216}},{"id":"1130453","location":{"lat":48.8763,"lon":2.3139}},{"id":"1121283","location":{"lat":48.8733,"lon":2.3213}},{"id":"1143992","location":{"lat":48.8713,"lon":2.3226}},{"id":"1020782","location":{"lat":48.8717,"lon":2.3198}},{"id":"1109136","location":{"lat":48.8732,"lon":2.3214}},{"id":"1107406","location":{"lat":48.87,"lon":2.3189}},{"id":"1001454","location":{"lat":48.8717,"lon":2.322}},{"id":"1031405","location":{"lat":48.8733,"lon":2.3181}},{"id":"1099019","location":{"lat":48.8712,"lon":2.3184}},{"id":"1040631","location":{"lat":48.8722,"lon":2.3231}},{"id":"1030463","location":{"lat":48.8725,"lon":2.3218}},{"id":"1033191","location":{"lat":48.8736,"lon":2.3213}},{"id":"1133959","location":{"lat":48.873,"lon":2.3163}},{"id":"1004770","location":{"lat":48.8788,"lon":2.3171}},{"id":"1129651","location":{"lat":48.8713,"lon":2.3226}},{"id":"1121101","location":{"lat":48.8701,"lon":2.3183}},{"id":"1119751","location":{"lat":48.8703,"lon":2.3212}},{"id":"1137030","location":{"lat":48.8729,"lon":2.3223}},{"id":"1134263","location":{"lat":48.8764,"lon":2.3142}},{"id":"1133530","location":{"lat":48.873,"lon":2.3176}},{"id":"1142237","location":{"lat":48.8713,"lon":2.3226}},{"id":"1030487","location":{"lat":48.8701,"lon":2.3191}},{"id":"1004647","location":{"lat":48.874,"lon":2.3186}},{"id":"1004716","location":{"lat":48.8737,"lon":2.3172}},{"id":"1144936","location":{"lat":48.8772,"lon":2.3165}},{"id":"1134666","location":{"lat":48.874,"lon":2.3184}},{"id":"1006725","location":{"lat":48.8736,"lon":2.3158}},{"id":"1092502","location":{"lat":48.8754,"lon":2.323}},{"id":"1008001","location":{"lat":48.8749,"lon":2.3158}},{"id":"1144493","location":{"lat":48.873,"lon":2.3124}},{"id":"1147114","location":{"lat":48.8738,"lon":2.3165}},{"id":"1147721","location":{"lat":0.0,"lon":0.0}},{"id":"1003152","location":{"lat":48.8763,"lon":2.3205}},{"id":"1110450","location":{"lat":48.8735,"lon":2.3142}},{"id":"1070260","location":{"lat":48.8742,"lon":2.3206}},{"id":"1132451","location":{"lat":48.8739,"lon":2.3193}},{"id":"1122595","location":{"lat":48.8743,"lon":2.3212}},{"id":"1134348","location":{"lat":48.8749,"lon":2.3211}},{"id":"1127201","location":{"lat":48.8732,"lon":2.3131}},{"id":"1138580","location":{"lat":48.8751,"lon":2.3211}},{"id":"1143039","location":{"lat":48.8731,"lon":2.3135}},{"id":"1132224","location":{"lat":48.8746,"lon":2.3226}},{"id":"1095177","location":{"lat":48.877,"lon":2.3175}},{"id":"1111407","location":{"lat":48.8745,"lon":2.3219}},{"id":"1117925","location":{"lat":48.8739,"lon":2.3178}},{"id":"1135294","location":{"lat":48.8737,"lon":2.3138}},{"id":"1031534","location":{"lat":48.8735,"lon":2.3143}},{"id":"1047944","location":{"lat":48.8739,"lon":2.3195}},{"id":"1050281","location":{"lat":48.873,"lon":2.3157}},{"id":"1054024","location":{"lat":48.8754,"lon":2.3236}},{"id":"1040973","location":{"lat":48.8765,"lon":2.3173}},{"id":"1063338","location":{"lat":48.8752,"lon":2.3171}},{"id":"1031918","location":{"lat":48.8739,"lon":2.3178}},{"id":"1145151","location":{"lat":48.8739,"lon":2.3193}},{"id":"1054036","location":{"lat":48.8748,"lon":2.3215}},{"id":"1004708","location":{"lat":48.875,"lon":2.3203}},{"id":"1002561","location":{"lat":48.8744,"lon":2.3174}},{"id":"1005880","location":{"lat":48.8738,"lon":2.3161}},{"id":"1144485","location":{"lat":48.8736,"lon":2.3139}},{"id":"1116199","location":{"lat":48.8737,"lon":2.3142}},{"id":"1123435","location":{"lat":48.8738,"lon":2.318}},{"id":"1124213","location":{"lat":48.8743,"lon":2.3182}},{"id":"startvehicule1","location":{"lat":48.78,"lon":2.43}},{"id":"startvehicule2","location":{"lat":48.78,"lon":2.43}},{"id":"endvehicule1","location":{"lat":48.78,"lon":2.43}},{"id":"endvehicule2","location":{"lat":48.78,"lon":2.43}}],"units":[{"id":"kg","label":"kg"},{"id":"l","label":"l"},{"id":"qte","label":"qte"}],"services":[{"id":"1002100_EMP_ 28_1FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":3,"minimum_lapse":120.0,"activity":{"point_id":"1002100","duration":120,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":30600,"end":45000,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1147052_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_SAV_ 84_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147052_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139149_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1104396_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109631_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139151_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1005088_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1052132_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1052132_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080537_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1033652_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1052132_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1004332_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1001821_ASC_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1139149_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1062118_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1062118_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1062118_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1062118_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1035112_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1035112_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1035112_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1144968_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144968_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144968_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136835_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1134687_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1135600_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":49.35},{"unit_id":"l","value":601.6},{"unit_id":"qte","value":47.0}],"visits_number":3,"minimum_lapse":376.0,"activity":{"point_id":"1135600","duration":376,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135600_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":49.35},{"unit_id":"l","value":601.6},{"unit_id":"qte","value":47.0}],"visits_number":3,"minimum_lapse":376.0,"activity":{"point_id":"1135600","duration":376,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133576_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1133576_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1133576_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1138821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138821_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134687_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139149_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1134687_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1080091_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080091_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1094392_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1094392","duration":90,"setup_duration":120,"timewindows":[{"start":25200,"end":45000,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":25200,"end":45000,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":25200,"end":45000,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":25200,"end":45000,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":25200,"end":45000,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080091_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1134687_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1136835_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1137046_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137046_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137046_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1066596_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004005_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1041519_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.59},{"unit_id":"l","value":2.625},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1041519","duration":28,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1131694_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1131694_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_TAP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_DIF_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1130633_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1130633_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130633_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102925_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102928_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1116088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1131649_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1136697_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030515_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030517_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030517_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132871_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1132871","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148306_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030517_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136835_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1126467_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1130633_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102928_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1130633_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1136697_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1130633_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1136697_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132871_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1132871","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1066596_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1066596_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1004005_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1116088_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1030517_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148306_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030517_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1041519_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.59},{"unit_id":"l","value":2.625},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1041519","duration":28,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1004005_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139151_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139151_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1005088_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1139149_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1147052_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147052_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109631_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1004332_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1080537_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102925_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102925_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102928_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1116088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080537_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1052132_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004332_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1033652_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1052132_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1052132_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1130723_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":115.2},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1130723","duration":72,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1099009_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_CLI_168_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005056_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1005056","duration":26,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005056_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1005056","duration":26,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1080067_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1095726_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122952_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1126324_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1126324_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1126324_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1126467_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1126467_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1130723_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":115.2},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1130723","duration":72,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1126324_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1030348_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1124513_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1122952_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124103_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124103_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122952_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1124103_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124513_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1004005_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1137046_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1131694_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137046_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137046_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1007882_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030515_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004005_TAP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142617_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031446_ASC_ 84_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1139292_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1142617_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148428_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_DIF_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1131394_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1131394_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137715_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1007287_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":15.5},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":50.0}],"visits_number":3,"minimum_lapse":200.0,"activity":{"point_id":"1007287","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127546_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123348_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103574_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1087334_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1058540_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1058540","duration":180,"setup_duration":120,"timewindows":[{"start":30600,"end":72000,"day_index":0},{"start":30600,"end":72000,"day_index":1},{"start":30600,"end":72000,"day_index":2},{"start":30600,"end":72000,"day_index":3},{"start":30600,"end":72000,"day_index":4}]},"type":"service"},{"id":"1054230_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1103574_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1120609_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123348_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1120609_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1070749_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1121283_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1143992_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1143992_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1121283_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1001454_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1070735_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031405_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031405_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1070749_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030463_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1033191_ASC_ 84_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":400.0,"activity":{"point_id":"1033191","duration":400,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1040631_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1054230_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1133951_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1133959_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_ASC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1129651_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1129651_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121101_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004770_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1119751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121101_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119751_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054230_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1137030_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137030_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1133530_PCP_ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137030_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1142237_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142237_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1133530_PH _ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133530_SAV_ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1005919_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1137030_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1142237_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142237_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_CLI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_INI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1030487_SNC_ 42_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":2,"minimum_lapse":180.0,"activity":{"point_id":"1030487","duration":180,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1007287_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":15.5},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":50.0}],"visits_number":3,"minimum_lapse":200.0,"activity":{"point_id":"1007287","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1096970_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1031405_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031405_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1070735_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131394_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1123348_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1120609_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1123348_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127546_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1131394_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1127546_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030348_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1143992_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1143992_SAV_ 84_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1020782_CLI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_INI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1001454_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1103574_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103574_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_INI_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1005919_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_ASC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1144594_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1121101_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119751_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1096970_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1121101_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1129651_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1133951_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1129651_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121101_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1142237_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1058540_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1058540","duration":180,"setup_duration":120,"timewindows":[{"start":30600,"end":72000,"day_index":0},{"start":30600,"end":72000,"day_index":1},{"start":30600,"end":72000,"day_index":2},{"start":30600,"end":72000,"day_index":3},{"start":30600,"end":72000,"day_index":4}]},"type":"service"},{"id":"1109631_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1142237_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137030_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1137030_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1142237_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137030_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137030_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1132589_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1142237_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120539_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_DIF_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_SNC_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004716_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1004716","duration":104,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":30600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1144936_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144936_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144936_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1006725_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1006725_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1092502_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_CLI_ 84_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_TAP_ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1006725_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1008001_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1144936_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147114_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.94},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1147114","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147114_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":2.94},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1147114","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147721_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1003152_SNC_ 42_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_BOB_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_PH _ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122595_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1122595_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1122595_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1110450_SAV_ 14_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1134348_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127201_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":14.28},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1127201","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127201_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":14.28},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1127201","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143039_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":26.56},{"unit_id":"l","value":105.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":800.0,"activity":{"point_id":"1143039","duration":800,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132224_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1132224_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1110450_PH _ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095177_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":51.2},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1095177","duration":32,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1111407_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":37.5},{"unit_id":"l","value":145.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1111407","duration":500,"setup_duration":120,"timewindows":[{"start":25200,"end":50400,"day_index":0},{"start":25200,"end":50400,"day_index":1},{"start":25200,"end":50400,"day_index":2},{"start":25200,"end":50400,"day_index":3},{"start":25200,"end":50400,"day_index":4}]},"type":"service"},{"id":"1117925_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132224_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1138580_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_CLI_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_PH _ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1031534_SAV_ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1047944_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":31.0,"activity":{"point_id":"1047944","duration":31,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1047944_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":31.0,"activity":{"point_id":"1047944","duration":31,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1050281_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":52.0,"activity":{"point_id":"1050281","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054024_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":102.0},{"unit_id":"qte","value":3.0}],"visits_number":12,"minimum_lapse":270.0,"activity":{"point_id":"1054024","duration":270,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1050281_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":52.0,"activity":{"point_id":"1050281","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1040973_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":6,"minimum_lapse":186.0,"activity":{"point_id":"1040973","duration":186,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1063338_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1063338","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031534_CLI_ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1070260_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1031918_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1135294_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_EMP_ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054036_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":180.0,"activity":{"point_id":"1054036","duration":180,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1003152_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_ASC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1110450_BOB_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004708_LPL_ 28_3FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":182.0,"activity":{"point_id":"1004708","duration":182,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004708_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":182.0,"activity":{"point_id":"1004708","duration":182,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1145151_EMP_ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054036_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":180.0,"activity":{"point_id":"1054036","duration":180,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002561_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002561_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005880_EMP_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005880_SAV_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002561_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_LPL_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116199_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":18.46},{"unit_id":"l","value":42.0},{"unit_id":"qte","value":76.0}],"visits_number":3,"minimum_lapse":304.0,"activity":{"point_id":"1116199","duration":304,"setup_duration":120,"timewindows":[{"start":33300,"end":61200,"day_index":0},{"start":33300,"end":61200,"day_index":1},{"start":33300,"end":61200,"day_index":2},{"start":33300,"end":61200,"day_index":3},{"start":33300,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123435_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123435","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_CLI_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143039_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":26.56},{"unit_id":"l","value":105.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":800.0,"activity":{"point_id":"1143039","duration":800,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005880_PH _ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005880_SNC_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031918_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1031918_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1004647_SNC_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"}],"vehicles":[{"id":"vehicule1","start_point_id":"startvehicule1","end_point_id":"endvehicule1","router_mode":"car","speed_multiplier":0.75,"cost_time_multiplier":1.0,"router_dimension":"time","sequence_timewindows":[{"start":21600,"end":45000,"day_index":0},{"start":21600,"end":45000,"day_index":1},{"start":21600,"end":45000,"day_index":2},{"start":21600,"end":45000,"day_index":3},{"start":21600,"end":45000,"day_index":4}],"capacities":[{"unit_id":"kg","limit":850.0},{"unit_id":"l","limit":7435.0},{"unit_id":"qte","limit":9999.0}],"unavailable_work_day_indices":[5,6],"traffic":true,"track":true,"motorway":true,"toll":true,"max_walk_distance":750,"approach":"unrestricted"},{"id":"vehicule2","start_point_id":"startvehicule2","end_point_id":"endvehicule2","router_mode":"car","speed_multiplier":0.75,"cost_time_multiplier":1.0,"router_dimension":"time","sequence_timewindows":[{"start":21600,"end":45000,"day_index":0},{"start":21600,"end":45000,"day_index":1},{"start":21600,"end":45000,"day_index":2},{"start":21600,"end":45000,"day_index":3},{"start":21600,"end":45000,"day_index":4}],"capacities":[{"unit_id":"kg","limit":1210.0},{"unit_id":"l","limit":6254.0},{"unit_id":"qte","limit":9999.0}],"unavailable_work_day_indices":[5,6],"traffic":true,"track":true,"motorway":true,"toll":true,"max_walk_distance":750,"approach":"unrestricted"}],"configuration":{"preprocessing":{"use_periodic_heuristic":true,"prefer_short_segment":true,"partition_method":"balanced_kmeans","partition_metric":"duration"},"resolution":{"same_point_day":true,"solver_parameter":-1,"duration":225000,"initial_time_out":112500,"time_out_multiplier":2},"schedule":{"range_indices":{"start":0,"end":83}},"restitution":{"csv":true,"intermediate_solutions":false}}}} \ No newline at end of file +{"vrp":{"points":[{"id":"1002100","location":{"lat":48.865,"lon":2.3054}},{"id":"1103548","location":{"lat":48.8711,"lon":2.3079}},{"id":"1142617","location":{"lat":48.8756,"lon":2.302}},{"id":"1147052","location":{"lat":48.8758,"lon":2.3074}},{"id":"1104396","location":{"lat":48.8776,"lon":2.3056}},{"id":"1139292","location":{"lat":48.8767,"lon":2.3032}},{"id":"1139149","location":{"lat":48.8767,"lon":2.3073}},{"id":"1118656","location":{"lat":48.8732,"lon":2.3049}},{"id":"1123712","location":{"lat":48.8755,"lon":2.3023}},{"id":"1120539","location":{"lat":48.8739,"lon":2.303}},{"id":"1109631","location":{"lat":48.8774,"lon":2.3047}},{"id":"1139151","location":{"lat":48.8767,"lon":2.3071}},{"id":"1005088","location":{"lat":48.8714,"lon":2.307}},{"id":"1054022","location":{"lat":48.8735,"lon":2.3095}},{"id":"1052132","location":{"lat":48.8733,"lon":2.3058}},{"id":"1080067","location":{"lat":48.8755,"lon":2.3024}},{"id":"1080537","location":{"lat":48.8732,"lon":2.3057}},{"id":"1001821","location":{"lat":48.8721,"lon":2.3043}},{"id":"1033652","location":{"lat":48.8758,"lon":2.3031}},{"id":"1127811","location":{"lat":48.8768,"lon":2.3091}},{"id":"1031446","location":{"lat":48.8723,"lon":2.3033}},{"id":"1004332","location":{"lat":48.8733,"lon":2.3056}},{"id":"1030348","location":{"lat":48.875,"lon":2.3051}},{"id":"1062118","location":{"lat":48.873,"lon":2.305}},{"id":"1035112","location":{"lat":48.8755,"lon":2.3023}},{"id":"1001140","location":{"lat":48.8776,"lon":2.3038}},{"id":"1144968","location":{"lat":48.8749,"lon":2.304}},{"id":"1136835","location":{"lat":48.8732,"lon":2.3051}},{"id":"1133790","location":{"lat":48.879,"lon":2.3043}},{"id":"1133878","location":{"lat":48.8785,"lon":2.3039}},{"id":"1007882","location":{"lat":48.8738,"lon":2.2965}},{"id":"1020596","location":{"lat":48.8664,"lon":2.31}},{"id":"1064282","location":{"lat":48.8731,"lon":2.3072}},{"id":"1134687","location":{"lat":48.8759,"lon":2.3077}},{"id":"1135600","location":{"lat":48.8768,"lon":2.3092}},{"id":"1133576","location":{"lat":48.8768,"lon":2.3091}},{"id":"1138821","location":{"lat":48.8749,"lon":2.3035}},{"id":"1066596","location":{"lat":48.8722,"lon":2.2967}},{"id":"1080091","location":{"lat":48.8787,"lon":2.3051}},{"id":"1094392","location":{"lat":48.8732,"lon":2.3131}},{"id":"1071805","location":{"lat":48.8755,"lon":2.3022}},{"id":"1064291","location":{"lat":48.8731,"lon":2.3072}},{"id":"1137046","location":{"lat":48.8732,"lon":2.3051}},{"id":"1131694","location":{"lat":48.8744,"lon":2.2984}},{"id":"1005035","location":{"lat":48.8786,"lon":2.3131}},{"id":"1004005","location":{"lat":48.8733,"lon":2.3062}},{"id":"1041519","location":{"lat":48.8755,"lon":2.3022}},{"id":"1148428","location":{"lat":0.0,"lon":0.0}},{"id":"1119178","location":{"lat":48.8726,"lon":2.304}},{"id":"1030515","location":{"lat":48.8789,"lon":2.303}},{"id":"1130633","location":{"lat":48.8755,"lon":2.3023}},{"id":"1132792","location":{"lat":48.8744,"lon":2.2984}},{"id":"1124356","location":{"lat":48.8753,"lon":2.3047}},{"id":"1121089","location":{"lat":48.8769,"lon":2.3074}},{"id":"1102925","location":{"lat":48.8732,"lon":2.3131}},{"id":"1102928","location":{"lat":48.8732,"lon":2.3131}},{"id":"1105871","location":{"lat":48.872,"lon":2.3039}},{"id":"1116088","location":{"lat":48.8768,"lon":2.3091}},{"id":"1109290","location":{"lat":48.8747,"lon":2.2982}},{"id":"1131649","location":{"lat":48.8775,"lon":2.2997}},{"id":"1136697","location":{"lat":48.8732,"lon":2.3051}},{"id":"1030517","location":{"lat":48.8751,"lon":2.3064}},{"id":"1132871","location":{"lat":48.8732,"lon":2.3051}},{"id":"1148306","location":{"lat":0.0,"lon":0.0}},{"id":"1126467","location":{"lat":48.8768,"lon":2.3091}},{"id":"1130723","location":{"lat":48.8768,"lon":2.3006}},{"id":"1099009","location":{"lat":48.874,"lon":2.2984}},{"id":"1095726","location":{"lat":48.8777,"lon":2.2994}},{"id":"1005056","location":{"lat":48.8776,"lon":2.3038}},{"id":"1122952","location":{"lat":48.8738,"lon":2.3005}},{"id":"1126324","location":{"lat":48.8768,"lon":2.3091}},{"id":"1124513","location":{"lat":48.8732,"lon":2.3051}},{"id":"1124103","location":{"lat":48.873,"lon":2.3047}},{"id":"1131394","location":{"lat":48.8747,"lon":2.3239}},{"id":"1133951","location":{"lat":48.8704,"lon":2.3211}},{"id":"1137715","location":{"lat":48.8698,"lon":2.3182}},{"id":"1132589","location":{"lat":48.8739,"lon":2.3214}},{"id":"1145751","location":{"lat":48.8715,"lon":2.3236}},{"id":"1070749","location":{"lat":48.8712,"lon":2.3194}},{"id":"1070735","location":{"lat":48.8703,"lon":2.3176}},{"id":"1002504","location":{"lat":48.8696,"lon":2.3188}},{"id":"1007287","location":{"lat":48.8707,"lon":2.3199}},{"id":"1005919","location":{"lat":48.8698,"lon":2.3178}},{"id":"1143914","location":{"lat":48.8693,"lon":2.3201}},{"id":"1144594","location":{"lat":48.8764,"lon":2.3083}},{"id":"1127546","location":{"lat":48.8692,"lon":2.3209}},{"id":"1123348","location":{"lat":48.8742,"lon":2.3171}},{"id":"1103574","location":{"lat":48.8711,"lon":2.3185}},{"id":"1087334","location":{"lat":48.8724,"lon":2.3183}},{"id":"1088315","location":{"lat":48.8762,"lon":2.3135}},{"id":"1054230","location":{"lat":48.8697,"lon":2.3198}},{"id":"1058540","location":{"lat":48.8701,"lon":2.3209}},{"id":"1106440","location":{"lat":48.87,"lon":2.3185}},{"id":"1120609","location":{"lat":48.8729,"lon":2.3228}},{"id":"1119750","location":{"lat":48.8693,"lon":2.3195}},{"id":"1107065","location":{"lat":48.8708,"lon":2.3202}},{"id":"1096970","location":{"lat":48.8733,"lon":2.3193}},{"id":"1124357","location":{"lat":48.8716,"lon":2.3216}},{"id":"1130453","location":{"lat":48.8763,"lon":2.3139}},{"id":"1121283","location":{"lat":48.8733,"lon":2.3213}},{"id":"1143992","location":{"lat":48.8713,"lon":2.3226}},{"id":"1020782","location":{"lat":48.8717,"lon":2.3198}},{"id":"1109136","location":{"lat":48.8732,"lon":2.3214}},{"id":"1107406","location":{"lat":48.87,"lon":2.3189}},{"id":"1001454","location":{"lat":48.8717,"lon":2.322}},{"id":"1031405","location":{"lat":48.8733,"lon":2.3181}},{"id":"1099019","location":{"lat":48.8712,"lon":2.3184}},{"id":"1040631","location":{"lat":48.8722,"lon":2.3231}},{"id":"1030463","location":{"lat":48.8725,"lon":2.3218}},{"id":"1033191","location":{"lat":48.8736,"lon":2.3213}},{"id":"1133959","location":{"lat":48.873,"lon":2.3163}},{"id":"1004770","location":{"lat":48.8788,"lon":2.3171}},{"id":"1129651","location":{"lat":48.8713,"lon":2.3226}},{"id":"1121101","location":{"lat":48.8701,"lon":2.3183}},{"id":"1119751","location":{"lat":48.8703,"lon":2.3212}},{"id":"1137030","location":{"lat":48.8729,"lon":2.3223}},{"id":"1134263","location":{"lat":48.8764,"lon":2.3142}},{"id":"1133530","location":{"lat":48.873,"lon":2.3176}},{"id":"1142237","location":{"lat":48.8713,"lon":2.3226}},{"id":"1030487","location":{"lat":48.8701,"lon":2.3191}},{"id":"1004647","location":{"lat":48.874,"lon":2.3186}},{"id":"1004716","location":{"lat":48.8737,"lon":2.3172}},{"id":"1144936","location":{"lat":48.8772,"lon":2.3165}},{"id":"1134666","location":{"lat":48.874,"lon":2.3184}},{"id":"1006725","location":{"lat":48.8736,"lon":2.3158}},{"id":"1092502","location":{"lat":48.8754,"lon":2.323}},{"id":"1008001","location":{"lat":48.8749,"lon":2.3158}},{"id":"1144493","location":{"lat":48.873,"lon":2.3124}},{"id":"1147114","location":{"lat":48.8738,"lon":2.3165}},{"id":"1147721","location":{"lat":0.0,"lon":0.0}},{"id":"1003152","location":{"lat":48.8763,"lon":2.3205}},{"id":"1110450","location":{"lat":48.8735,"lon":2.3142}},{"id":"1070260","location":{"lat":48.8742,"lon":2.3206}},{"id":"1132451","location":{"lat":48.8739,"lon":2.3193}},{"id":"1122595","location":{"lat":48.8743,"lon":2.3212}},{"id":"1134348","location":{"lat":48.8749,"lon":2.3211}},{"id":"1127201","location":{"lat":48.8732,"lon":2.3131}},{"id":"1138580","location":{"lat":48.8751,"lon":2.3211}},{"id":"1143039","location":{"lat":48.8731,"lon":2.3135}},{"id":"1132224","location":{"lat":48.8746,"lon":2.3226}},{"id":"1095177","location":{"lat":48.877,"lon":2.3175}},{"id":"1111407","location":{"lat":48.8745,"lon":2.3219}},{"id":"1117925","location":{"lat":48.8739,"lon":2.3178}},{"id":"1135294","location":{"lat":48.8737,"lon":2.3138}},{"id":"1031534","location":{"lat":48.8735,"lon":2.3143}},{"id":"1047944","location":{"lat":48.8739,"lon":2.3195}},{"id":"1050281","location":{"lat":48.873,"lon":2.3157}},{"id":"1054024","location":{"lat":48.8754,"lon":2.3236}},{"id":"1040973","location":{"lat":48.8765,"lon":2.3173}},{"id":"1063338","location":{"lat":48.8752,"lon":2.3171}},{"id":"1031918","location":{"lat":48.8739,"lon":2.3178}},{"id":"1145151","location":{"lat":48.8739,"lon":2.3193}},{"id":"1054036","location":{"lat":48.8748,"lon":2.3215}},{"id":"1004708","location":{"lat":48.875,"lon":2.3203}},{"id":"1002561","location":{"lat":48.8744,"lon":2.3174}},{"id":"1005880","location":{"lat":48.8738,"lon":2.3161}},{"id":"1144485","location":{"lat":48.8736,"lon":2.3139}},{"id":"1116199","location":{"lat":48.8737,"lon":2.3142}},{"id":"1123435","location":{"lat":48.8738,"lon":2.318}},{"id":"1124213","location":{"lat":48.8743,"lon":2.3182}},{"id":"startvehicule1","location":{"lat":48.78,"lon":2.43}},{"id":"startvehicule2","location":{"lat":48.78,"lon":2.43}},{"id":"endvehicule1","location":{"lat":48.78,"lon":2.43}},{"id":"endvehicule2","location":{"lat":48.78,"lon":2.43}}],"units":[{"id":"kg","label":"kg"},{"id":"l","label":"l"},{"id":"qte","label":"qte"}],"services":[{"id":"1002100_EMP_ 28_1FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":3,"minimum_lapse":120.0,"activity":{"point_id":"1002100","duration":120,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":30600,"end":45000,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1147052_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_SAV_ 84_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147052_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139149_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1104396_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109631_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139151_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1005088_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1052132_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1052132_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080537_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1033652_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1052132_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1004332_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1001821_ASC_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1139149_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1062118_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1062118_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1062118_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1062118_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1062118","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1035112_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1035112_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1035112_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1035112","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1144968_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144968_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144968_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.852},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1144968","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136835_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133790_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133790","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133878_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":10.4},{"unit_id":"l","value":49.248},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1133878","duration":64,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1134687_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1135600_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":49.35},{"unit_id":"l","value":601.6},{"unit_id":"qte","value":47.0}],"visits_number":3,"minimum_lapse":376.0,"activity":{"point_id":"1135600","duration":376,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135600_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":49.35},{"unit_id":"l","value":601.6},{"unit_id":"qte","value":47.0}],"visits_number":3,"minimum_lapse":376.0,"activity":{"point_id":"1135600","duration":376,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133576_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1133576_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1133576_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":59.52},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":192.0}],"visits_number":3,"minimum_lapse":768.0,"activity":{"point_id":"1133576","duration":768,"setup_duration":120,"timewindows":[{"start":23400,"end":34200,"day_index":0},{"start":23400,"end":34200,"day_index":1},{"start":23400,"end":34200,"day_index":2},{"start":23400,"end":34200,"day_index":3},{"start":23400,"end":34200,"day_index":4}]},"type":"service"},{"id":"1138821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138821_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.25},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":125.0}],"visits_number":3,"minimum_lapse":500.0,"activity":{"point_id":"1138821","duration":500,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134687_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139149_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1134687_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1080091_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080091_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1094392_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1094392","duration":90,"setup_duration":120,"timewindows":[{"start":25200,"end":45000,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":25200,"end":45000,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":25200,"end":45000,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":25200,"end":45000,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":25200,"end":45000,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080091_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":64.74},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":204.0}],"visits_number":3,"minimum_lapse":816.0,"activity":{"point_id":"1080091","duration":816,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1134687_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1134687","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1136835_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1137046_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137046_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137046_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1066596_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004005_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1041519_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.59},{"unit_id":"l","value":2.625},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1041519","duration":28,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1131694_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1131694_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_TAP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_DIF_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1130633_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1130633_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130633_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102925_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102928_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1116088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1131649_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1136697_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030515_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030517_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030517_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132871_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1132871","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148306_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030517_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136835_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.44},{"unit_id":"l","value":8.4},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1136835","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1126467_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1130633_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102928_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1130633_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1136697_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131649_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.36},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":16.0}],"visits_number":3,"minimum_lapse":64.0,"activity":{"point_id":"1131649","duration":64,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1130633_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130633_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1130633","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1136697_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1136697_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":312.0,"activity":{"point_id":"1136697","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132871_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":4.8},{"unit_id":"l","value":94.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1132871","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1066596_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1071805_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":130.0,"activity":{"point_id":"1071805","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":70200,"day_index":0},{"start":28800,"end":70200,"day_index":1},{"start":28800,"end":70200,"day_index":2},{"start":28800,"end":70200,"day_index":3},{"start":28800,"end":70200,"day_index":4}]},"type":"service"},{"id":"1066596_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1066596_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1066596","duration":100,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064291_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1064291_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1064291","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1004005_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1064282_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":52.0,"activity":{"point_id":"1064282","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":70200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":70200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":70200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":70200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":70200,"day_index":4}]},"type":"service"},{"id":"1116088_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1030517_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148306_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148306_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.62},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":22.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1148306","duration":88,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1001140_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1001140","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":68400,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":68400,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":68400,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":68400,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030517_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030517_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.095},{"unit_id":"l","value":1.53},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1030517","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1041519_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.59},{"unit_id":"l","value":2.625},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1041519","duration":28,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1004005_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139151_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1139151_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139151_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1139151","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1005088_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005088_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":6.2},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1005088","duration":16,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1139149_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1139149","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":72000,"day_index":0},{"start":32400,"end":72000,"day_index":1},{"start":32400,"end":72000,"day_index":2},{"start":32400,"end":72000,"day_index":3},{"start":32400,"end":72000,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1147052_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147052_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.08},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1147052","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109631_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109631_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1104396_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":5.5},{"unit_id":"l","value":5.0},{"unit_id":"qte","value":5.0}],"visits_number":1,"minimum_lapse":20.0,"activity":{"point_id":"1104396","duration":20,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1004332_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1080537_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1001821_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1001821_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":15.0,"activity":{"point_id":"1001821","duration":15,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1102925_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102925_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102925","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1102928_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1102928","duration":90,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":63000,"end":73800,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":63000,"end":73800,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":63000,"end":73800,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":63000,"end":73800,"day_index":3},{"start":21600,"end":32400,"day_index":4},{"start":63000,"end":73800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1105871_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080537_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1116088_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":20.29},{"unit_id":"l","value":37.8},{"unit_id":"qte","value":64.0}],"visits_number":3,"minimum_lapse":256.0,"activity":{"point_id":"1116088","duration":256,"setup_duration":120,"timewindows":[{"start":28800,"end":43200,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":28800,"end":43200,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":28800,"end":43200,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":28800,"end":43200,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":28800,"end":43200,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1080537_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.55},{"unit_id":"l","value":140.8},{"unit_id":"qte","value":11.0}],"visits_number":3,"minimum_lapse":88.0,"activity":{"point_id":"1080537","duration":88,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1052132_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004332_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1033652_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1052132_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1052132_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.7},{"unit_id":"l","value":179.2},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":112.0,"activity":{"point_id":"1052132","duration":112,"setup_duration":120,"timewindows":[{"start":34200,"end":61200,"day_index":0},{"start":34200,"end":61200,"day_index":1},{"start":34200,"end":61200,"day_index":2},{"start":34200,"end":61200,"day_index":3},{"start":34200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1130723_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":115.2},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1130723","duration":72,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004005_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1007882_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1099009_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_CLI_168_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1105871_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":41.8},{"unit_id":"l","value":117.8},{"unit_id":"qte","value":19.0}],"visits_number":6,"minimum_lapse":247.0,"activity":{"point_id":"1105871","duration":247,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109290_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":50.6},{"unit_id":"l","value":142.6},{"unit_id":"qte","value":23.0}],"visits_number":6,"minimum_lapse":299.0,"activity":{"point_id":"1109290","duration":299,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099009_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":160.0,"activity":{"point_id":"1099009","duration":160,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005056_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1005056","duration":26,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1005056_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1005056","duration":26,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1033652_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":21.0},{"unit_id":"l","value":256.0},{"unit_id":"qte","value":20.0}],"visits_number":3,"minimum_lapse":160.0,"activity":{"point_id":"1033652","duration":160,"setup_duration":120,"timewindows":[{"start":30600,"end":45000,"day_index":0},{"start":30600,"end":45000,"day_index":1},{"start":30600,"end":45000,"day_index":2},{"start":30600,"end":45000,"day_index":3},{"start":30600,"end":45000,"day_index":4}]},"type":"service"},{"id":"1054022_SNC_ 7_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1054022","duration":90,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1080067_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1080067_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":33.0},{"unit_id":"l","value":93.0},{"unit_id":"qte","value":15.0}],"visits_number":12,"minimum_lapse":195.0,"activity":{"point_id":"1080067","duration":195,"setup_duration":120,"timewindows":[{"start":21600,"end":46800,"day_index":0},{"start":21600,"end":46800,"day_index":1},{"start":21600,"end":46800,"day_index":2},{"start":21600,"end":46800,"day_index":3},{"start":21600,"end":46800,"day_index":4}]},"type":"service"},{"id":"1095726_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095726_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1095726","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_EMP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121089_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":19.5},{"unit_id":"l","value":92.34},{"unit_id":"qte","value":15.0}],"visits_number":6,"minimum_lapse":120.0,"activity":{"point_id":"1121089","duration":120,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122952_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1126324_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1126324_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1126324_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1127811_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1126467_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1126467_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1126467","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":30600,"day_index":0},{"start":21600,"end":30600,"day_index":1},{"start":21600,"end":30600,"day_index":2},{"start":21600,"end":30600,"day_index":3},{"start":21600,"end":30600,"day_index":4}]},"type":"service"},{"id":"1130723_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":115.2},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1130723","duration":72,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132792_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":16.6},{"unit_id":"l","value":66.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1132792","duration":500,"setup_duration":120,"timewindows":[{"start":21600,"end":61200,"day_index":0},{"start":21600,"end":61200,"day_index":1},{"start":21600,"end":61200,"day_index":2},{"start":21600,"end":61200,"day_index":3},{"start":21600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1126324_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":9.45},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":45.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1126324","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":36000,"day_index":0},{"start":32400,"end":36000,"day_index":1},{"start":32400,"end":36000,"day_index":2},{"start":32400,"end":36000,"day_index":3},{"start":32400,"end":36000,"day_index":4}]},"type":"service"},{"id":"1030348_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1124513_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1122952_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124103_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124103_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122952_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":15.4},{"unit_id":"l","value":43.4},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":91.0,"activity":{"point_id":"1122952","duration":91,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124356_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1124356","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1124103_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":3.9},{"unit_id":"l","value":18.468},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1124103","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124513_BOB_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1124513_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":2.94},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1124513","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":68400,"day_index":0},{"start":36000,"end":68400,"day_index":1},{"start":36000,"end":68400,"day_index":2},{"start":36000,"end":68400,"day_index":3},{"start":36000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1004005_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030348_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1137046_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1131694_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137046_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131694_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":11.7},{"unit_id":"l","value":55.404},{"unit_id":"qte","value":9.0}],"visits_number":3,"minimum_lapse":72.0,"activity":{"point_id":"1131694","duration":72,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":52200,"end":61200,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":52200,"end":61200,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":52200,"end":61200,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":52200,"end":61200,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":52200,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127811_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1123712_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127811_PH _ 7_4FA","quantities":[{"unit_id":"kg","value":29.76},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":96.0}],"visits_number":12,"minimum_lapse":384.0,"activity":{"point_id":"1127811","duration":384,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137046_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1137046","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005035_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1007882_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1007882_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":26.4},{"unit_id":"l","value":74.4},{"unit_id":"qte","value":12.0}],"visits_number":6,"minimum_lapse":117.0,"activity":{"point_id":"1007882","duration":117,"setup_duration":120,"timewindows":[{"start":21600,"end":43200,"day_index":0},{"start":21600,"end":43200,"day_index":1},{"start":21600,"end":43200,"day_index":2},{"start":21600,"end":43200,"day_index":3},{"start":21600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1020596_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.2},{"unit_id":"l","value":15.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1020596","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1030515_SNC_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1030515_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1030515","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005035_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":7.64},{"unit_id":"l","value":16.8},{"unit_id":"qte","value":24.0}],"visits_number":3,"minimum_lapse":132.0,"activity":{"point_id":"1005035","duration":132,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004005_TAP_ 28_4FA","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":12.4},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":26.0,"activity":{"point_id":"1004005","duration":26,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123712_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1123712","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142617_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_PCP_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1142617_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_SNC_ 14_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_CLI_ 84_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1139292_DIF_ 84_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031446_ASC_ 84_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1139292_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1142617_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1148428_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119178_LPL_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_DIF_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119178_CLI_ 28_4FA","quantities":[{"unit_id":"kg","value":12.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":250.0}],"visits_number":3,"minimum_lapse":3250.0,"activity":{"point_id":"1119178","duration":3250,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1118656_PCP_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1118656_PH _ 14_4FA","quantities":[{"unit_id":"kg","value":14.88},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":48.0}],"visits_number":6,"minimum_lapse":192.0,"activity":{"point_id":"1118656","duration":192,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103548_TAP_ 7_4FA","quantities":[{"unit_id":"kg","value":9.7},{"unit_id":"l","value":37.02},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1103548","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1142617_SAV_ 14_4FA","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":24.0,"activity":{"point_id":"1142617","duration":24,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1139292_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":1.9},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":180.0,"activity":{"point_id":"1139292","duration":180,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1148428_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":22.1},{"unit_id":"l","value":104.652},{"unit_id":"qte","value":17.0}],"visits_number":3,"minimum_lapse":136.0,"activity":{"point_id":"1148428","duration":136,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031446_TAP_ 14_4FA","quantities":[{"unit_id":"kg","value":4.89},{"unit_id":"l","value":19.55},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":100.0,"activity":{"point_id":"1031446","duration":100,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1131394_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1131394_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137715_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1007287_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":15.5},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":50.0}],"visits_number":3,"minimum_lapse":200.0,"activity":{"point_id":"1007287","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127546_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123348_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103574_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1087334_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1058540_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1058540","duration":180,"setup_duration":120,"timewindows":[{"start":30600,"end":72000,"day_index":0},{"start":30600,"end":72000,"day_index":1},{"start":30600,"end":72000,"day_index":2},{"start":30600,"end":72000,"day_index":3},{"start":30600,"end":72000,"day_index":4}]},"type":"service"},{"id":"1054230_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1103574_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1120609_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123348_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1120609_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1070749_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1121283_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1143992_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1143992_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1121283_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1001454_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1070735_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031405_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031405_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1070749_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030463_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1033191_ASC_ 84_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":400.0,"activity":{"point_id":"1033191","duration":400,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1040631_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1054230_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1133951_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1133959_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_ASC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1129651_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1129651_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121101_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004770_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1119751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121101_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119751_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054230_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1137030_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137030_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1133530_PCP_ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137030_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1142237_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142237_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1133530_PH _ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133530_SAV_ 84_4FF","quantities":[{"unit_id":"kg","value":7.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":36.0}],"visits_number":1,"minimum_lapse":144.0,"activity":{"point_id":"1133530","duration":144,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1005919_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1137030_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1142237_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1142237_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_CLI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_INI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1030487_SNC_ 42_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":2,"minimum_lapse":180.0,"activity":{"point_id":"1030487","duration":180,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1007287_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":15.5},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":50.0}],"visits_number":3,"minimum_lapse":200.0,"activity":{"point_id":"1007287","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070749_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":7.35},{"unit_id":"l","value":89.6},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1070749","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1096970_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1096970_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1031405_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1031405_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.375},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1031405","duration":4,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1070735_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1145751","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1131394_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1123348_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1120609_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120609_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.6},{"unit_id":"l","value":6.0},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1120609","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1123348_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070735_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.7},{"unit_id":"l","value":7.0},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":28.0,"activity":{"point_id":"1070735","duration":28,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1123348_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123348","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1127546_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1137715_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":130.0,"activity":{"point_id":"1137715","duration":130,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1131394_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1131394_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.475},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1131394","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1127546_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127546_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.8},{"unit_id":"l","value":0.75},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1127546","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1109136_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1109136_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1109136","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1121283_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004332_BOB_ 14_4FA","quantities":[{"unit_id":"kg","value":1.11},{"unit_id":"l","value":1.125},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004332","duration":12,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1030348_PH _ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030348_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1030348_BOB_ 7_4FA","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":12,"minimum_lapse":156.0,"activity":{"point_id":"1030348","duration":156,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1107406_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1143992_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1030463_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.66},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":46.0}],"visits_number":3,"minimum_lapse":184.0,"activity":{"point_id":"1030463","duration":184,"setup_duration":120,"timewindows":[{"start":30000,"end":68400,"day_index":0},{"start":30000,"end":68400,"day_index":1},{"start":30000,"end":68400,"day_index":2},{"start":30000,"end":68400,"day_index":3},{"start":30000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1143992_SAV_ 84_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1143992_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1143992","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":62100,"day_index":0},{"start":32400,"end":62100,"day_index":1},{"start":32400,"end":62100,"day_index":2},{"start":32400,"end":62100,"day_index":3},{"start":32400,"end":62100,"day_index":4}]},"type":"service"},{"id":"1020782_CLI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_INI_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1001454_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1001454_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":9.0,"activity":{"point_id":"1001454","duration":9,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1103574_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1103574_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":7.305},{"unit_id":"l","value":14.7},{"unit_id":"qte","value":23.0}],"visits_number":3,"minimum_lapse":92.0,"activity":{"point_id":"1103574","duration":92,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143914_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":8.94},{"unit_id":"l","value":33.9},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1143914","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004770_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.93},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1004770","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":48600,"end":64800,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":48600,"end":64800,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":48600,"end":64800,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":48600,"end":64800,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":48600,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_INI_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1096970_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1005919_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1005919_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_ASC_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1002504_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1144594_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144594_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":6.64},{"unit_id":"l","value":26.4},{"unit_id":"qte","value":2.0}],"visits_number":6,"minimum_lapse":200.0,"activity":{"point_id":"1144594","duration":200,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1107065_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1121101_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1121101_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1119750_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119750_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1119751_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119751_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1119751","duration":12,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1119750_EMP_ 14_4FF","quantities":[{"unit_id":"kg","value":39.0},{"unit_id":"l","value":184.68},{"unit_id":"qte","value":30.0}],"visits_number":6,"minimum_lapse":240.0,"activity":{"point_id":"1119750","duration":240,"setup_duration":120,"timewindows":[{"start":30600,"end":39600,"day_index":0},{"start":51300,"end":56700,"day_index":0},{"start":30600,"end":39600,"day_index":1},{"start":51300,"end":56700,"day_index":1},{"start":30600,"end":39600,"day_index":2},{"start":51300,"end":56700,"day_index":2},{"start":30600,"end":39600,"day_index":3},{"start":51300,"end":56700,"day_index":3},{"start":30600,"end":39600,"day_index":4},{"start":51300,"end":56700,"day_index":4}]},"type":"service"},{"id":"1096970_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":11.0},{"unit_id":"l","value":10.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1096970","duration":40,"setup_duration":120,"timewindows":[{"start":28800,"end":57600,"day_index":0},{"start":28800,"end":57600,"day_index":1},{"start":28800,"end":57600,"day_index":2},{"start":28800,"end":57600,"day_index":3},{"start":28800,"end":57600,"day_index":4}]},"type":"service"},{"id":"1121101_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1129651_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1133951_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_DIF_ 84_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133951_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":136.0},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":360.0,"activity":{"point_id":"1133951","duration":360,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1133959_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1133959","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1129651_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1129651","duration":8,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121101_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":3.3},{"unit_id":"l","value":3.0},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":12.0,"activity":{"point_id":"1121101","duration":12,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1099019_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1099019_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002504_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":77.0},{"unit_id":"l","value":217.0},{"unit_id":"qte","value":35.0}],"visits_number":12,"minimum_lapse":455.0,"activity":{"point_id":"1002504","duration":455,"setup_duration":120,"timewindows":[{"start":21600,"end":32400,"day_index":0},{"start":21600,"end":32400,"day_index":1},{"start":21600,"end":32400,"day_index":2},{"start":21600,"end":32400,"day_index":3},{"start":21600,"end":32400,"day_index":4}]},"type":"service"},{"id":"1107406_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1121283_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1121283","duration":8,"setup_duration":120,"timewindows":[{"start":36000,"end":64800,"day_index":0},{"start":36000,"end":64800,"day_index":1},{"start":36000,"end":64800,"day_index":2},{"start":36000,"end":64800,"day_index":3},{"start":36000,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124357_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":90.0,"activity":{"point_id":"1124357","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1130453_BOB_ 14_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_CLI_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1130453_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":52.8},{"unit_id":"l","value":148.8},{"unit_id":"qte","value":24.0}],"visits_number":6,"minimum_lapse":312.0,"activity":{"point_id":"1130453","duration":312,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132589_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1005919_BOB_ 7_4FF","quantities":[{"unit_id":"kg","value":89.46},{"unit_id":"l","value":276.0},{"unit_id":"qte","value":60.0}],"visits_number":12,"minimum_lapse":274.0,"activity":{"point_id":"1005919","duration":274,"setup_duration":120,"timewindows":[{"start":24300,"end":43200,"day_index":0},{"start":24300,"end":43200,"day_index":1},{"start":24300,"end":43200,"day_index":2},{"start":24300,"end":43200,"day_index":3},{"start":24300,"end":43200,"day_index":4}]},"type":"service"},{"id":"1142237_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1088315_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":2.8},{"unit_id":"l","value":30.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1088315","duration":180,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_CLI_ 84_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1087334_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":60.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":360.0,"activity":{"point_id":"1087334","duration":360,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054230_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":0.0},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":1,"minimum_lapse":80.0,"activity":{"point_id":"1054230","duration":80,"setup_duration":120,"timewindows":[{"start":36000,"end":61200,"day_index":0},{"start":36000,"end":61200,"day_index":1},{"start":36000,"end":61200,"day_index":2},{"start":36000,"end":61200,"day_index":3},{"start":36000,"end":61200,"day_index":4}]},"type":"service"},{"id":"1058540_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":180.0,"activity":{"point_id":"1058540","duration":180,"setup_duration":120,"timewindows":[{"start":30600,"end":72000,"day_index":0},{"start":30600,"end":72000,"day_index":1},{"start":30600,"end":72000,"day_index":2},{"start":30600,"end":72000,"day_index":3},{"start":30600,"end":72000,"day_index":4}]},"type":"service"},{"id":"1109631_PCP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1109631","duration":40,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":32400,"end":43200,"day_index":4}]},"type":"service"},{"id":"1142237_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137030_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1020782_SAV_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1020782_DIF_ 42_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1040631_TAP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1040631_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":23.1},{"unit_id":"l","value":21.0},{"unit_id":"qte","value":21.0}],"visits_number":3,"minimum_lapse":35.0,"activity":{"point_id":"1040631","duration":35,"setup_duration":120,"timewindows":[{"start":27000,"end":68400,"day_index":0},{"start":27000,"end":68400,"day_index":1},{"start":27000,"end":68400,"day_index":2},{"start":27000,"end":68400,"day_index":3},{"start":27000,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PH _ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1107065_PCP_ 14_4FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":4.0,"activity":{"point_id":"1107065","duration":4,"setup_duration":120,"timewindows":[{"start":39600,"end":68400,"day_index":0},{"start":39600,"end":68400,"day_index":1},{"start":39600,"end":68400,"day_index":2},{"start":39600,"end":68400,"day_index":3},{"start":39600,"end":68400,"day_index":4}]},"type":"service"},{"id":"1099019_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":43.364},{"unit_id":"l","value":123.8},{"unit_id":"qte","value":21.0}],"visits_number":6,"minimum_lapse":273.0,"activity":{"point_id":"1099019","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1020782_SNC_ 14_4FF","quantities":[{"unit_id":"kg","value":68.77},{"unit_id":"l","value":126.0},{"unit_id":"qte","value":217.0}],"visits_number":6,"minimum_lapse":556.0,"activity":{"point_id":"1020782","duration":556,"setup_duration":120,"timewindows":[{"start":27000,"end":57600,"day_index":0},{"start":27000,"end":57600,"day_index":1},{"start":27000,"end":57600,"day_index":2},{"start":27000,"end":57600,"day_index":3},{"start":27000,"end":57600,"day_index":4}]},"type":"service"},{"id":"1137030_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1106440_TAP_ 7_4FF","quantities":[{"unit_id":"kg","value":3.32},{"unit_id":"l","value":13.2},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":200.0,"activity":{"point_id":"1106440","duration":200,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1142237_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1137030_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1134263_BOB_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1134263_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":12.06},{"unit_id":"l","value":75.6},{"unit_id":"qte","value":36.0}],"visits_number":3,"minimum_lapse":144.0,"activity":{"point_id":"1134263","duration":144,"setup_duration":120,"timewindows":[{"start":30600,"end":66600,"day_index":0},{"start":30600,"end":66600,"day_index":1},{"start":30600,"end":66600,"day_index":2},{"start":30600,"end":66600,"day_index":3},{"start":30600,"end":66600,"day_index":4}]},"type":"service"},{"id":"1137030_EMP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1137030_PCP_ 28_4FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1137030","duration":40,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":63000,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":63000,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":63000,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":63000,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1132589_PH _ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1132589_SAV_ 28_4FF","quantities":[{"unit_id":"kg","value":48.4},{"unit_id":"l","value":136.4},{"unit_id":"qte","value":22.0}],"visits_number":12,"minimum_lapse":286.0,"activity":{"point_id":"1132589","duration":286,"setup_duration":120,"timewindows":[{"start":23400,"end":30600,"day_index":0},{"start":23400,"end":30600,"day_index":1},{"start":23400,"end":30600,"day_index":2},{"start":23400,"end":30600,"day_index":3},{"start":23400,"end":30600,"day_index":4}]},"type":"service"},{"id":"1142237_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":9.1},{"unit_id":"l","value":43.092},{"unit_id":"qte","value":7.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1142237","duration":56,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1107406_SNC_ 28_4FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1107406","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":61200,"day_index":0},{"start":28800,"end":61200,"day_index":1},{"start":28800,"end":61200,"day_index":2},{"start":28800,"end":61200,"day_index":3},{"start":28800,"end":61200,"day_index":4}]},"type":"service"},{"id":"1120539_EMP_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1120539_SAV_ 28_4FA","quantities":[{"unit_id":"kg","value":2.2},{"unit_id":"l","value":2.0},{"unit_id":"qte","value":2.0}],"visits_number":3,"minimum_lapse":8.0,"activity":{"point_id":"1120539","duration":8,"setup_duration":120,"timewindows":[{"start":34200,"end":64800,"day_index":0},{"start":34200,"end":64800,"day_index":1},{"start":34200,"end":64800,"day_index":2},{"start":34200,"end":64800,"day_index":3},{"start":34200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_DIF_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_SNC_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004716_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":104.0,"activity":{"point_id":"1004716","duration":104,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":30600,"end":43200,"day_index":4}]},"type":"service"},{"id":"1144936_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144936_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144936_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1006725_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1006725_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1092502_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1092502_CLI_ 84_3FF","quantities":[{"unit_id":"kg","value":2.01},{"unit_id":"l","value":12.6},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1092502","duration":24,"setup_duration":120,"timewindows":[{"start":30600,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":30600,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":30600,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":30600,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":30600,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_TAP_ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1006725_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":3.15},{"unit_id":"l","value":38.4},{"unit_id":"qte","value":3.0}],"visits_number":3,"minimum_lapse":24.0,"activity":{"point_id":"1006725","duration":24,"setup_duration":120,"timewindows":[{"start":32400,"end":65700,"day_index":0},{"start":32400,"end":65700,"day_index":1},{"start":32400,"end":65700,"day_index":2},{"start":32400,"end":65700,"day_index":3},{"start":32400,"end":65700,"day_index":4}]},"type":"service"},{"id":"1008001_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1008001_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.5},{"unit_id":"l","value":29.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":115.0,"activity":{"point_id":"1008001","duration":115,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1144936_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":10.85},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":35.0}],"visits_number":3,"minimum_lapse":140.0,"activity":{"point_id":"1144936","duration":140,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144493_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":76.8},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1144493","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147114_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.94},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1147114","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147114_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":2.94},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":14.0}],"visits_number":3,"minimum_lapse":56.0,"activity":{"point_id":"1147114","duration":56,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1147721_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1147721_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":17.6},{"unit_id":"l","value":49.6},{"unit_id":"qte","value":8.0}],"visits_number":3,"minimum_lapse":104.0,"activity":{"point_id":"1147721","duration":104,"setup_duration":120,"timewindows":[{"start":32400,"end":46800,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":46800,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":46800,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":46800,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":46800,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1003152_SNC_ 42_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_BOB_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_PH _ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134666_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134666","duration":16,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1132451_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1132451","duration":90,"setup_duration":120,"timewindows":[{"start":34200,"end":45000,"day_index":0},{"start":50400,"end":64800,"day_index":0},{"start":34200,"end":45000,"day_index":1},{"start":50400,"end":64800,"day_index":1},{"start":34200,"end":45000,"day_index":2},{"start":50400,"end":64800,"day_index":2},{"start":34200,"end":45000,"day_index":3},{"start":50400,"end":64800,"day_index":3},{"start":34200,"end":45000,"day_index":4},{"start":50400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1122595_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1122595_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1122595_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":3,"minimum_lapse":169.0,"activity":{"point_id":"1122595","duration":169,"setup_duration":120,"timewindows":[{"start":28800,"end":39600,"day_index":0},{"start":28800,"end":39600,"day_index":1},{"start":28800,"end":39600,"day_index":2},{"start":28800,"end":39600,"day_index":3},{"start":28800,"end":39600,"day_index":4}]},"type":"service"},{"id":"1110450_SAV_ 14_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1070260_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1134348_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127201_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":14.28},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1127201","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1127201_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":14.28},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":68.0}],"visits_number":3,"minimum_lapse":272.0,"activity":{"point_id":"1127201","duration":272,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143039_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":26.56},{"unit_id":"l","value":105.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":800.0,"activity":{"point_id":"1143039","duration":800,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1134348_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":4.4},{"unit_id":"l","value":4.0},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":16.0,"activity":{"point_id":"1134348","duration":16,"setup_duration":120,"timewindows":[{"start":30600,"end":61200,"day_index":0},{"start":30600,"end":61200,"day_index":1},{"start":30600,"end":61200,"day_index":2},{"start":30600,"end":61200,"day_index":3},{"start":30600,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132224_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1132224_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1110450_PH _ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1110450_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1095177_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":51.2},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1095177","duration":32,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1111407_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":37.5},{"unit_id":"l","value":145.0},{"unit_id":"qte","value":5.0}],"visits_number":6,"minimum_lapse":500.0,"activity":{"point_id":"1111407","duration":500,"setup_duration":120,"timewindows":[{"start":25200,"end":50400,"day_index":0},{"start":25200,"end":50400,"day_index":1},{"start":25200,"end":50400,"day_index":2},{"start":25200,"end":50400,"day_index":3},{"start":25200,"end":50400,"day_index":4}]},"type":"service"},{"id":"1117925_EMP_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1117925_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":48.0,"activity":{"point_id":"1117925","duration":48,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1132224_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":1.1},{"unit_id":"l","value":1.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1132224","duration":4,"setup_duration":120,"timewindows":[{"start":34200,"end":70200,"day_index":0},{"start":34200,"end":70200,"day_index":1},{"start":34200,"end":70200,"day_index":2},{"start":34200,"end":70200,"day_index":3},{"start":34200,"end":70200,"day_index":4}]},"type":"service"},{"id":"1138580_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_CLI_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1135294_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_PH _ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1031534_SAV_ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1047944_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":31.0,"activity":{"point_id":"1047944","duration":31,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1047944_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":1.05},{"unit_id":"l","value":12.8},{"unit_id":"qte","value":1.0}],"visits_number":6,"minimum_lapse":31.0,"activity":{"point_id":"1047944","duration":31,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1050281_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":52.0,"activity":{"point_id":"1050281","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1054024_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":6.3},{"unit_id":"l","value":102.0},{"unit_id":"qte","value":3.0}],"visits_number":12,"minimum_lapse":270.0,"activity":{"point_id":"1054024","duration":270,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1050281_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":8.8},{"unit_id":"l","value":24.8},{"unit_id":"qte","value":4.0}],"visits_number":6,"minimum_lapse":52.0,"activity":{"point_id":"1050281","duration":52,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1040973_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":28.6},{"unit_id":"l","value":80.6},{"unit_id":"qte","value":13.0}],"visits_number":6,"minimum_lapse":186.0,"activity":{"point_id":"1040973","duration":186,"setup_duration":120,"timewindows":[{"start":21600,"end":75600,"day_index":0},{"start":21600,"end":75600,"day_index":1},{"start":21600,"end":75600,"day_index":2},{"start":21600,"end":75600,"day_index":3},{"start":21600,"end":75600,"day_index":4}]},"type":"service"},{"id":"1063338_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":34.0},{"unit_id":"qte","value":1.0}],"visits_number":12,"minimum_lapse":90.0,"activity":{"point_id":"1063338","duration":90,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031534_CLI_ 84_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1070260_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":5.25},{"unit_id":"l","value":64.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":40.0,"activity":{"point_id":"1070260","duration":40,"setup_duration":120,"timewindows":[{"start":25200,"end":64800,"day_index":0},{"start":25200,"end":64800,"day_index":1},{"start":25200,"end":64800,"day_index":2},{"start":25200,"end":64800,"day_index":3},{"start":25200,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1031918_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1135294_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":0.1},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":4.0,"activity":{"point_id":"1135294","duration":4,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1138580_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":5.2},{"unit_id":"l","value":24.624},{"unit_id":"qte","value":4.0}],"visits_number":3,"minimum_lapse":32.0,"activity":{"point_id":"1138580","duration":32,"setup_duration":120,"timewindows":[{"start":32400,"end":64800,"day_index":0},{"start":32400,"end":64800,"day_index":1},{"start":32400,"end":64800,"day_index":2},{"start":32400,"end":64800,"day_index":3},{"start":32400,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_EMP_ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054036_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":180.0,"activity":{"point_id":"1054036","duration":180,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1003152_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_ASC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1003152_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1031534_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.1},{"unit_id":"l","value":25.6},{"unit_id":"qte","value":2.0}],"visits_number":1,"minimum_lapse":16.0,"activity":{"point_id":"1031534","duration":16,"setup_duration":120,"timewindows":[{"start":32400,"end":68400,"day_index":0},{"start":32400,"end":68400,"day_index":1},{"start":32400,"end":68400,"day_index":2},{"start":32400,"end":68400,"day_index":3},{"start":32400,"end":68400,"day_index":4}]},"type":"service"},{"id":"1110450_BOB_ 7_3FF","quantities":[{"unit_id":"kg","value":46.2},{"unit_id":"l","value":130.2},{"unit_id":"qte","value":21.0}],"visits_number":12,"minimum_lapse":273.0,"activity":{"point_id":"1110450","duration":273,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004708_LPL_ 28_3FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":182.0,"activity":{"point_id":"1004708","duration":182,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1004708_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":0.4},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":5.0}],"visits_number":3,"minimum_lapse":182.0,"activity":{"point_id":"1004708","duration":182,"setup_duration":120,"timewindows":[{"start":32400,"end":43200,"day_index":0},{"start":50400,"end":61200,"day_index":0},{"start":32400,"end":43200,"day_index":1},{"start":50400,"end":61200,"day_index":1},{"start":32400,"end":43200,"day_index":2},{"start":50400,"end":61200,"day_index":2},{"start":32400,"end":43200,"day_index":3},{"start":50400,"end":61200,"day_index":3},{"start":32400,"end":43200,"day_index":4},{"start":50400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1145151_EMP_ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1054036_SNC_ 7_3FF","quantities":[{"unit_id":"kg","value":4.2},{"unit_id":"l","value":68.0},{"unit_id":"qte","value":2.0}],"visits_number":12,"minimum_lapse":180.0,"activity":{"point_id":"1054036","duration":180,"setup_duration":120,"timewindows":[{"start":27000,"end":72000,"day_index":0},{"start":27000,"end":72000,"day_index":1},{"start":27000,"end":72000,"day_index":2},{"start":27000,"end":72000,"day_index":3},{"start":27000,"end":72000,"day_index":4}]},"type":"service"},{"id":"1003152_TAP_ 7_3FF","quantities":[{"unit_id":"kg","value":7.2},{"unit_id":"l","value":141.0},{"unit_id":"qte","value":3.0}],"visits_number":2,"minimum_lapse":270.0,"activity":{"point_id":"1003152","duration":270,"setup_duration":120,"timewindows":[{"start":27900,"end":64800,"day_index":0},{"start":27900,"end":64800,"day_index":1},{"start":27900,"end":64800,"day_index":2},{"start":27900,"end":64800,"day_index":3},{"start":27900,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002561_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1002561_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005880_EMP_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005880_SAV_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1002561_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":9.6},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":3,"minimum_lapse":46.0,"activity":{"point_id":"1002561","duration":46,"setup_duration":120,"timewindows":[{"start":29700,"end":64800,"day_index":0},{"start":29700,"end":64800,"day_index":1},{"start":29700,"end":64800,"day_index":2},{"start":29700,"end":64800,"day_index":3},{"start":29700,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1145151_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":7.8},{"unit_id":"l","value":36.936},{"unit_id":"qte","value":6.0}],"visits_number":6,"minimum_lapse":48.0,"activity":{"point_id":"1145151","duration":48,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_LPL_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1116199_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":18.46},{"unit_id":"l","value":42.0},{"unit_id":"qte","value":76.0}],"visits_number":3,"minimum_lapse":304.0,"activity":{"point_id":"1116199","duration":304,"setup_duration":120,"timewindows":[{"start":33300,"end":61200,"day_index":0},{"start":33300,"end":61200,"day_index":1},{"start":33300,"end":61200,"day_index":2},{"start":33300,"end":61200,"day_index":3},{"start":33300,"end":61200,"day_index":4}]},"type":"service"},{"id":"1123435_SNC_ 28_3FF","quantities":[{"unit_id":"kg","value":2.4},{"unit_id":"l","value":47.0},{"unit_id":"qte","value":1.0}],"visits_number":3,"minimum_lapse":90.0,"activity":{"point_id":"1123435","duration":90,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_BOB_ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1124213_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":13.2},{"unit_id":"l","value":37.2},{"unit_id":"qte","value":6.0}],"visits_number":3,"minimum_lapse":78.0,"activity":{"point_id":"1124213","duration":78,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_CLI_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1144485_PCP_ 28_3FF","quantities":[{"unit_id":"kg","value":2.56},{"unit_id":"l","value":0.0},{"unit_id":"qte","value":32.0}],"visits_number":3,"minimum_lapse":416.0,"activity":{"point_id":"1144485","duration":416,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1143039_TAP_ 14_3FF","quantities":[{"unit_id":"kg","value":26.56},{"unit_id":"l","value":105.6},{"unit_id":"qte","value":8.0}],"visits_number":6,"minimum_lapse":800.0,"activity":{"point_id":"1143039","duration":800,"setup_duration":120,"timewindows":[{"start":28800,"end":64800,"day_index":0},{"start":28800,"end":64800,"day_index":1},{"start":28800,"end":64800,"day_index":2},{"start":28800,"end":64800,"day_index":3},{"start":28800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1005880_PH _ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1005880_SNC_ 42_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":41.72},{"unit_id":"qte","value":7.0}],"visits_number":2,"minimum_lapse":61.0,"activity":{"point_id":"1005880","duration":61,"setup_duration":120,"timewindows":[{"start":32400,"end":61200,"day_index":0},{"start":32400,"end":61200,"day_index":1},{"start":32400,"end":61200,"day_index":2},{"start":32400,"end":61200,"day_index":3},{"start":32400,"end":61200,"day_index":4}]},"type":"service"},{"id":"1031918_BOB_ 14_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1031918_PH _ 28_3FF","quantities":[{"unit_id":"kg","value":22.0},{"unit_id":"l","value":62.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":277.0,"activity":{"point_id":"1031918","duration":277,"setup_duration":120,"timewindows":[{"start":32400,"end":63000,"day_index":0},{"start":32400,"end":63000,"day_index":1},{"start":32400,"end":63000,"day_index":2},{"start":32400,"end":63000,"day_index":3},{"start":32400,"end":63000,"day_index":4}]},"type":"service"},{"id":"1004647_SNC_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_SAV_ 28_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PH _ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"},{"id":"1004647_PCP_ 14_3FF","quantities":[{"unit_id":"kg","value":10.5},{"unit_id":"l","value":128.0},{"unit_id":"qte","value":10.0}],"visits_number":6,"minimum_lapse":80.0,"activity":{"point_id":"1004647","duration":80,"setup_duration":120,"timewindows":[{"start":37800,"end":64800,"day_index":0},{"start":37800,"end":64800,"day_index":1},{"start":37800,"end":64800,"day_index":2},{"start":37800,"end":64800,"day_index":3},{"start":37800,"end":64800,"day_index":4}]},"type":"service"}],"vehicles":[{"id":"vehicule1","start_point_id":"startvehicule1","end_point_id":"endvehicule1","router_mode":"car","speed_multiplier":0.75,"cost_time_multiplier":1.0,"router_dimension":"time","skills":[["vehicule1"]],"sequence_timewindows":[{"start":21600,"end":45000,"day_index":0},{"start":21600,"end":45000,"day_index":1},{"start":21600,"end":45000,"day_index":2},{"start":21600,"end":45000,"day_index":3},{"start":21600,"end":45000,"day_index":4}],"capacities":[{"unit_id":"kg","limit":850.0},{"unit_id":"l","limit":7435.0},{"unit_id":"qte","limit":9999.0}],"unavailable_work_day_indices":[5,6],"traffic":true,"track":true,"motorway":true,"toll":true,"max_walk_distance":750,"approach":"unrestricted"},{"id":"vehicule2","start_point_id":"startvehicule2","end_point_id":"endvehicule2","router_mode":"car","speed_multiplier":0.75,"cost_time_multiplier":1.0,"router_dimension":"time","skills":"vehicule1,vehicule2","sequence_timewindows":[{"start":21600,"end":45000,"day_index":0},{"start":21600,"end":45000,"day_index":1},{"start":21600,"end":45000,"day_index":2},{"start":21600,"end":45000,"day_index":3},{"start":21600,"end":45000,"day_index":4}],"capacities":[{"unit_id":"kg","limit":1210.0},{"unit_id":"l","limit":6254.0},{"unit_id":"qte","limit":9999.0}],"unavailable_work_day_indices":[5,6],"traffic":true,"track":true,"motorway":true,"toll":true,"max_walk_distance":750,"approach":"unrestricted"}],"configuration":{"preprocessing":{"use_periodic_heuristic":true,"prefer_short_segment":true,"partition_method":"balanced_kmeans","partition_metric":"duration"},"resolution":{"same_point_day":true,"solver_parameter":-1,"duration":225000,"initial_time_out":112500,"time_out_multiplier":2},"schedule":{"range_indices":{"start":0,"end":83}},"restitution":{"csv":true,"intermediate_solutions":false}}}} diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index f6a4e0cf9..a11403e1c 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -103,13 +103,25 @@ def infer_type_check(type) # passed, or if the type also implements a parse() method. type elsif type.is_a?(Enumerable) - ->(value) { value.respond_to?(:all?) && value.all? { |item| item.is_a? type[0] } } + lambda do |value| + value.is_a?(Enumerable) && value.all? do |val| + recursive_type_check(type.first, val) + end + end else # By default, do a simple type check ->(value) { value.is_a? type } end end + def recursive_type_check(type, value) + if type.is_a?(Enumerable) && value.is_a?(Enumerable) + value.all? { |val| recursive_type_check(type.first, val) } + else + !type.is_a?(Enumerable) && value.is_a?(type) + end + end + # Enforce symbolized keys for complex types # by wrapping the coercion method such that # any Hash objects in the immediate heirarchy diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index 41acb5f0d..e157748e9 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -620,6 +620,30 @@ def self.parsed?(value) expect(JSON.parse(last_response.body)).to eq(%w[a b c d]) end + it 'parses parameters with Array[Array[String]] type and coerce_with' do + subject.params do + requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val } + end + subject.post '/coerce_nested_strings' do + params[:values] + end + + post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json' + expect(last_response.status).to eq(201) + expect(JSON.parse(last_response.body)).to eq([%w[a b c d]]) + + post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json' + expect(last_response.status).to eq(201) + expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]]) + + post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json' + expect(last_response.status).to eq(201) + expect(JSON.parse(last_response.body)).to eq([[]]) + + post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json' + expect(last_response.status).to eq(400) + end + it 'parses parameters with Array[Integer] type' do subject.params do requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) } From d40d6977d651a0eb2f64795fdbab3a10353cbc1d Mon Sep 17 00:00:00 2001 From: Igor Victor Date: Tue, 1 Sep 2020 22:15:57 +0200 Subject: [PATCH 245/290] Add Truffleruby head to CI (#2099) --- .travis.yml | 6 ++++-- CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 134679cfe..48632523d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ sudo: false # see https://docs.travis-ci.com/user/customizing-the-build/#matching-jobs-with-allow_failures gemfile: +script: bundle exec rake spec + matrix: include: - rvm: 2.7.1 @@ -32,12 +34,10 @@ matrix: - rvm: 2.7.1 gemfile: gemfiles/multi_json.gemfile script: - - bundle exec rake - bundle exec rspec spec/integration/multi_json - rvm: 2.7.1 gemfile: gemfiles/multi_xml.gemfile script: - - bundle exec rake - bundle exec rspec spec/integration/multi_xml - rvm: 2.6.6 gemfile: Gemfile @@ -54,9 +54,11 @@ matrix: - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 + - rvm: truffleruby-head allow_failures: - rvm: ruby-head - rvm: jruby-head - rvm: rbx-3 + - rvm: truffleruby-head bundler_args: --without development diff --git a/CHANGELOG.md b/CHANGELOG.md index 609e4235a..0ec4232b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2099](https://github.com/ruby-grape/grape/pull/2099): Added truffleruby to Travis-CI - [@gogainda](https://github.com/gogainda). * [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). * [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). * [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc). From 416a7e15bdfda29cd4f9b335a911bb59a416be60 Mon Sep 17 00:00:00 2001 From: James Lamont Date: Mon, 14 Sep 2020 10:46:28 +1000 Subject: [PATCH 246/290] Fix retaining setup blocks when remounting APIs --- CHANGELOG.md | 1 + Gemfile | 1 + benchmark/remounting.rb | 47 +++++++++++++++++++++++++++++++++++++++++ lib/grape/api.rb | 2 +- spec/grape/api_spec.rb | 5 +++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 benchmark/remounting.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ec4232b7..08995a1a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe). * [#2096](https://github.com/ruby-grape/grape/pull/2096): Fix redundant dependency check - [@braktar](https://github.com/braktar). * [#2096](https://github.com/ruby-grape/grape/pull/2098): Fix nested coercion - [@braktar](https://github.com/braktar). +* [#2102](https://github.com/ruby-grape/grape/pull/2102): Fix retaining setup blocks when remounting APIs - [@jylamont](https://github.com/jylamont). ### 1.4.0 (2020/07/10) diff --git a/Gemfile b/Gemfile index 48a26eb17..7e70dbcfe 100644 --- a/Gemfile +++ b/Gemfile @@ -17,6 +17,7 @@ end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' diff --git a/benchmark/remounting.rb b/benchmark/remounting.rb new file mode 100644 index 000000000..a585c1d2e --- /dev/null +++ b/benchmark/remounting.rb @@ -0,0 +1,47 @@ +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +require 'grape' +require 'benchmark/memory' + +class VotingApi < Grape::API + logger Logger.new(STDOUT) + + helpers do + def logger + VotingApi.logger + end + end + + namespace 'votes' do + get do + logger + end + end +end + +class PostApi < Grape::API + mount VotingApi +end + +class CommentAPI < Grape::API + mount VotingApi +end + +env = Rack::MockRequest.env_for('/votes', method: 'GET') + +Benchmark.memory do |api| + calls = 1000 + + api.report('using Array') do + VotingApi.instance_variable_set(:@setup, []) + calls.times { PostApi.call(env) } + puts " setup size: #{VotingApi.instance_variable_get(:@setup).size}" + end + + api.report('using Set') do + VotingApi.instance_variable_set(:@setup, Set.new) + calls.times { PostApi.call(env) } + puts " setup size: #{VotingApi.instance_variable_get(:@setup).size}" + end + + api.compare! +end diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 45dd76c74..dd8ae8d89 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -30,7 +30,7 @@ def inherited(api, base_instance_parent = Grape::API::Instance) # an instance that will be used to create the set up but will not be mounted def initial_setup(base_instance_parent) @instances = [] - @setup = [] + @setup = Set.new @base_parent = base_instance_parent @base_instance = mount_instance end diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 4d9325671..0d722a4b1 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -1600,6 +1600,11 @@ def self.io expect(subject.io).to receive(:write).with(message) subject.logger.info 'this will be logged' end + + it 'does not unnecessarily retain duplicate setup blocks' do + subject.logger + expect { subject.logger }.to_not change(subject.instance_variable_get(:@setup), :size) + end end describe '.helpers' do From e79cf04277f60f3e21a71e3d3c02451087220590 Mon Sep 17 00:00:00 2001 From: Tim Connor Date: Tue, 29 Sep 2020 10:18:13 +1300 Subject: [PATCH 247/290] Lock rubocop-ast to < 0.7 The latest versions of rubocop-ast (>= 0.7) are incompatible with older versions of rubocop. Since grape currently use rubocop 0.84 we need to lock rubocop-ast to a compatible version --- Gemfile | 1 + gemfiles/multi_json.gemfile | 1 + gemfiles/multi_xml.gemfile | 1 + gemfiles/rack1.gemfile | 1 + gemfiles/rack2.gemfile | 1 + gemfiles/rack_edge.gemfile | 1 + gemfiles/rails_5.gemfile | 1 + gemfiles/rails_6.gemfile | 1 + gemfiles/rails_edge.gemfile | 1 + 9 files changed, 9 insertions(+) diff --git a/Gemfile b/Gemfile index 7e70dbcfe..6c8f23291 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index 0cb8517c1..279eabba5 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index d352caf98..ba20f9415 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile index 836f9c902..05e84c399 100644 --- a/gemfiles/rack1.gemfile +++ b/gemfiles/rack1.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rack2.gemfile b/gemfiles/rack2.gemfile index 9f92d32ff..accc673b3 100644 --- a/gemfiles/rack2.gemfile +++ b/gemfiles/rack2.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index 185c39391..d4271ef76 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index 894d2bd50..b32c7d3ac 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile index c7b9671f2..85e5f69fc 100644 --- a/gemfiles/rails_6.gemfile +++ b/gemfiles/rails_6.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index 24b5c5a8f..bfb0d3f72 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -11,6 +11,7 @@ group :development, :test do gem 'hashie' gem 'rake' gem 'rubocop', '0.84.0' + gem 'rubocop-ast', '< 0.7' gem 'rubocop-performance', require: false end From 4913534bcdb1f4f5bd521f84c30ac5ce6772ca46 Mon Sep 17 00:00:00 2001 From: Tim Connor Date: Fri, 18 Sep 2020 13:08:49 +1200 Subject: [PATCH 248/290] Move Grape::Endpoint#declared specs into own spec file --- spec/grape/endpoint/declared_spec.rb | 545 +++++++++++++++++++++++++++ spec/grape/endpoint_spec.rb | 534 -------------------------- 2 files changed, 545 insertions(+), 534 deletions(-) create mode 100644 spec/grape/endpoint/declared_spec.rb diff --git a/spec/grape/endpoint/declared_spec.rb b/spec/grape/endpoint/declared_spec.rb new file mode 100644 index 000000000..73d145a8e --- /dev/null +++ b/spec/grape/endpoint/declared_spec.rb @@ -0,0 +1,545 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Grape::Endpoint do + subject { Class.new(Grape::API) } + + def app + subject + end + + describe '#declared' do + before do + subject.format :json + subject.params do + requires :first + optional :second + optional :third, default: 'third-default' + optional :nested, type: Hash do + optional :fourth + optional :fifth + optional :nested_two, type: Hash do + optional :sixth + optional :nested_three, type: Hash do + optional :seventh + end + end + optional :nested_arr, type: Array do + optional :eighth + end + end + optional :arr, type: Array do + optional :nineth + end + end + end + + context 'when params are not built with default class' do + it 'returns an object that corresponds with the params class - hash with indifferent access' do + subject.params do + build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder + end + subject.get '/declared' do + d = declared(params, include_missing: true) + { declared_class: d.class.to_s } + end + + get '/declared?first=present' + expect(JSON.parse(last_response.body)['declared_class']).to eq('ActiveSupport::HashWithIndifferentAccess') + end + + it 'returns an object that corresponds with the params class - hashie mash' do + subject.params do + build_with Grape::Extensions::Hashie::Mash::ParamBuilder + end + subject.get '/declared' do + d = declared(params, include_missing: true) + { declared_class: d.class.to_s } + end + + get '/declared?first=present' + expect(JSON.parse(last_response.body)['declared_class']).to eq('Hashie::Mash') + end + + it 'returns an object that corresponds with the params class - hash' do + subject.params do + build_with Grape::Extensions::Hash::ParamBuilder + end + subject.get '/declared' do + d = declared(params, include_missing: true) + { declared_class: d.class.to_s } + end + + get '/declared?first=present' + expect(JSON.parse(last_response.body)['declared_class']).to eq('Hash') + end + end + + it 'should show nil for nested params if include_missing is true' do + subject.get '/declared' do + declared(params, include_missing: true) + end + + get '/declared?first=present' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil + end + + it 'does not work in a before filter' do + subject.before do + declared(params) + end + subject.get('/declared') { declared(params) } + + expect { get('/declared') }.to raise_error( + Grape::DSL::InsideRoute::MethodNotYetAvailable + ) + end + + it 'has as many keys as there are declared params' do + subject.get '/declared' do + declared(params) + end + get '/declared?first=present' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body).keys.size).to eq(5) + end + + it 'has a optional param with default value all the time' do + subject.get '/declared' do + declared(params) + end + get '/declared?first=one' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['third']).to eql('third-default') + end + + it 'builds nested params' do + subject.get '/declared' do + declared(params) + end + + get '/declared?first=present&nested[fourth]=1' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 4 + end + + it 'builds nested params when given array' do + subject.get '/dummy' do + end + subject.params do + requires :first + optional :second + optional :third, default: 'third-default' + optional :nested, type: Array do + optional :fourth + end + end + subject.get '/declared' do + declared(params) + end + + get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested'].size).to eq 2 + end + + context 'sets nested objects when the param is missing' do + it 'to be a hash when include_missing is true' do + subject.get '/declared' do + declared(params, include_missing: true) + end + + get '/declared?first=present' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested']).to eq({}) + end + + it 'to be an array when include_missing is true' do + subject.get '/declared' do + declared(params, include_missing: true) + end + + get '/declared?first=present' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['arr']).to be_a(Array) + end + + it 'to be an array when nested and include_missing is true' do + subject.get '/declared' do + declared(params, include_missing: true) + end + + get '/declared?first=present&nested[fourth]=1' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested']['nested_arr']).to be_a(Array) + end + + it 'to be nil when include_missing is false' do + subject.get '/declared' do + declared(params, include_missing: false) + end + + get '/declared?first=present' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['nested']).to be_nil + end + end + + it 'filters out any additional params that are given' do + subject.get '/declared' do + declared(params) + end + get '/declared?first=one&other=two' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body).key?(:other)).to eq false + end + + it 'stringifies if that option is passed' do + subject.get '/declared' do + declared(params, stringify: true) + end + + get '/declared?first=one&other=two' + expect(last_response.status).to eq(200) + expect(JSON.parse(last_response.body)['first']).to eq 'one' + end + + it 'does not include missing attributes if that option is passed' do + subject.get '/declared' do + error! 'expected nil', 400 if declared(params, include_missing: false).key?(:second) + '' + end + + get '/declared?first=one&other=two' + expect(last_response.status).to eq(200) + end + + it 'does not include renamed missing attributes if that option is passed' do + subject.params do + optional :renamed_original, as: :renamed + end + subject.get '/declared' do + error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed) + '' + end + + get '/declared?first=one&other=two' + expect(last_response.status).to eq(200) + end + + it 'includes attributes with value that evaluates to false' do + subject.params do + requires :first + optional :boolean + end + + subject.post '/declared' do + error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false + '' + end + + post '/declared', ::Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json' + expect(last_response.status).to eq(201) + end + + it 'includes attributes with value that evaluates to nil' do + subject.params do + requires :first + optional :second + end + + subject.post '/declared' do + error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil? + '' + end + + post '/declared', ::Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json' + expect(last_response.status).to eq(201) + end + + it 'includes missing attributes with defaults when there are nested hashes' do + subject.get '/dummy' do + end + + subject.params do + requires :first + optional :second + optional :third, default: nil + optional :nested, type: Hash do + optional :fourth, default: nil + optional :fifth, default: nil + requires :nested_nested, type: Hash do + optional :sixth, default: 'sixth-default' + optional :seven, default: nil + end + end + end + + subject.get '/declared' do + declared(params, include_missing: false) + end + + get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth' + json = JSON.parse(last_response.body) + expect(last_response.status).to eq(200) + expect(json['first']).to eq 'present' + expect(json['nested'].keys).to eq %w[fourth fifth nested_nested] + expect(json['nested']['fourth']).to eq '' + expect(json['nested']['nested_nested'].keys).to eq %w[sixth seven] + expect(json['nested']['nested_nested']['sixth']).to eq 'sixth' + end + + it 'does not include missing attributes when there are nested hashes' do + subject.get '/dummy' do + end + + subject.params do + requires :first + optional :second + optional :third + optional :nested, type: Hash do + optional :fourth + optional :fifth + end + end + + subject.get '/declared' do + declared(params, include_missing: false) + end + + get '/declared?first=present&nested[fourth]=4' + json = JSON.parse(last_response.body) + expect(last_response.status).to eq(200) + expect(json['first']).to eq 'present' + expect(json['nested'].keys).to eq %w[fourth] + expect(json['nested']['fourth']).to eq '4' + end + end + + describe '#declared; call from child namespace' do + before do + subject.format :json + subject.namespace :parent do + params do + requires :parent_name, type: String + end + + namespace ':parent_name' do + params do + requires :child_name, type: String + requires :child_age, type: Integer + end + + namespace ':child_name' do + params do + requires :grandchild_name, type: String + end + + get ':grandchild_name' do + { + 'params' => params, + 'without_parent_namespaces' => declared(params, include_parent_namespaces: false), + 'with_parent_namespaces' => declared(params, include_parent_namespaces: true) + } + end + end + end + end + + get '/parent/foo/bar/baz', child_age: 5, extra: 'hello' + end + + let(:parsed_response) { JSON.parse(last_response.body, symbolize_names: true) } + + it { expect(last_response.status).to eq 200 } + + context 'with include_parent_namespaces: false' do + it 'returns declared parameters only from current namespace' do + expect(parsed_response[:without_parent_namespaces]).to eq( + grandchild_name: 'baz' + ) + end + end + + context 'with include_parent_namespaces: true' do + it 'returns declared parameters from every parent namespace' do + expect(parsed_response[:with_parent_namespaces]).to eq( + parent_name: 'foo', + child_name: 'bar', + grandchild_name: 'baz', + child_age: 5 + ) + end + end + + context 'without declaration' do + it 'returns all requested parameters' do + expect(parsed_response[:params]).to eq( + parent_name: 'foo', + child_name: 'bar', + grandchild_name: 'baz', + child_age: 5, + extra: 'hello' + ) + end + end + end + + describe '#declared; from a nested mounted endpoint' do + before do + doubly_mounted = Class.new(Grape::API) + doubly_mounted.namespace :more do + params do + requires :y, type: Integer + end + route_param :y do + get do + { + params: params, + declared_params: declared(params) + } + end + end + end + + mounted = Class.new(Grape::API) + mounted.namespace :another do + params do + requires :mount_space, type: Integer + end + route_param :mount_space do + mount doubly_mounted + end + end + + subject.format :json + subject.namespace :something do + params do + requires :id, type: Integer + end + resource ':id' do + mount mounted + end + end + end + + it 'can access parent attributes' do + get '/something/123/another/456/more/789' + expect(last_response.status).to eq 200 + json = JSON.parse(last_response.body, symbolize_names: true) + + # test all three levels of params + expect(json[:declared_params][:y]).to eq 789 + expect(json[:declared_params][:mount_space]).to eq 456 + expect(json[:declared_params][:id]).to eq 123 + end + end + + describe '#declared; mixed nesting' do + before do + subject.format :json + subject.resource :users do + route_param :id, type: Integer, desc: 'ID desc' do + # Adding this causes route_setting(:declared_params) to be nil for the + # get block in namespace 'foo' below + get do + end + + namespace 'foo' do + get do + { + params: params, + declared_params: declared(params), + declared_params_no_parent: declared(params, include_parent_namespaces: false) + } + end + end + end + end + end + + it 'can access parent route_param' do + get '/users/123/foo', bar: 'bar' + expect(last_response.status).to eq 200 + json = JSON.parse(last_response.body, symbolize_names: true) + + expect(json[:declared_params][:id]).to eq 123 + expect(json[:declared_params_no_parent][:id]).to eq nil + end + end + + describe '#declared; with multiple route_param' do + before do + mounted = Class.new(Grape::API) + mounted.namespace :albums do + get do + declared(params) + end + end + + subject.format :json + subject.namespace :artists do + route_param :id, type: Integer do + get do + declared(params) + end + + params do + requires :filter, type: String + end + get :some_route do + declared(params) + end + end + + route_param :artist_id, type: Integer do + namespace :compositions do + get do + declared(params) + end + end + end + + route_param :compositor_id, type: Integer do + mount mounted + end + end + end + + it 'return only :id without :artist_id' do + get '/artists/1' + json = JSON.parse(last_response.body, symbolize_names: true) + + expect(json.key?(:id)).to be_truthy + expect(json.key?(:artist_id)).not_to be_truthy + end + + it 'return only :artist_id without :id' do + get '/artists/1/compositions' + json = JSON.parse(last_response.body, symbolize_names: true) + + expect(json.key?(:artist_id)).to be_truthy + expect(json.key?(:id)).not_to be_truthy + end + + it 'return :filter and :id parameters in declared for second enpoint inside route_param' do + get '/artists/1/some_route', filter: 'some_filter' + json = JSON.parse(last_response.body, symbolize_names: true) + + expect(json.key?(:filter)).to be_truthy + expect(json.key?(:id)).to be_truthy + expect(json.key?(:artist_id)).not_to be_truthy + end + + it 'return :compositor_id for mounter in route_param' do + get '/artists/1/albums' + json = JSON.parse(last_response.body, symbolize_names: true) + + expect(json.key?(:compositor_id)).to be_truthy + expect(json.key?(:id)).not_to be_truthy + expect(json.key?(:artist_id)).not_to be_truthy + end + end +end diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index a45abe36d..4bbeb070c 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -280,540 +280,6 @@ def app end end - describe '#declared' do - before do - subject.format :json - subject.params do - requires :first - optional :second - optional :third, default: 'third-default' - optional :nested, type: Hash do - optional :fourth - optional :fifth - optional :nested_two, type: Hash do - optional :sixth - optional :nested_three, type: Hash do - optional :seventh - end - end - optional :nested_arr, type: Array do - optional :eighth - end - end - optional :arr, type: Array do - optional :nineth - end - end - end - - context 'when params are not built with default class' do - it 'returns an object that corresponds with the params class - hash with indifferent access' do - subject.params do - build_with Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder - end - subject.get '/declared' do - d = declared(params, include_missing: true) - { declared_class: d.class.to_s } - end - - get '/declared?first=present' - expect(JSON.parse(last_response.body)['declared_class']).to eq('ActiveSupport::HashWithIndifferentAccess') - end - - it 'returns an object that corresponds with the params class - hashie mash' do - subject.params do - build_with Grape::Extensions::Hashie::Mash::ParamBuilder - end - subject.get '/declared' do - d = declared(params, include_missing: true) - { declared_class: d.class.to_s } - end - - get '/declared?first=present' - expect(JSON.parse(last_response.body)['declared_class']).to eq('Hashie::Mash') - end - - it 'returns an object that corresponds with the params class - hash' do - subject.params do - build_with Grape::Extensions::Hash::ParamBuilder - end - subject.get '/declared' do - d = declared(params, include_missing: true) - { declared_class: d.class.to_s } - end - - get '/declared?first=present' - expect(JSON.parse(last_response.body)['declared_class']).to eq('Hash') - end - end - - it 'should show nil for nested params if include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end - - get '/declared?first=present' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']['fourth']).to be_nil - end - - it 'does not work in a before filter' do - subject.before do - declared(params) - end - subject.get('/declared') { declared(params) } - - expect { get('/declared') }.to raise_error( - Grape::DSL::InsideRoute::MethodNotYetAvailable - ) - end - - it 'has as many keys as there are declared params' do - subject.get '/declared' do - declared(params) - end - get '/declared?first=present' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body).keys.size).to eq(5) - end - - it 'has a optional param with default value all the time' do - subject.get '/declared' do - declared(params) - end - get '/declared?first=one' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['third']).to eql('third-default') - end - - it 'builds nested params' do - subject.get '/declared' do - declared(params) - end - - get '/declared?first=present&nested[fourth]=1' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 4 - end - - it 'builds nested params when given array' do - subject.get '/dummy' do - end - subject.params do - requires :first - optional :second - optional :third, default: 'third-default' - optional :nested, type: Array do - optional :fourth - end - end - subject.get '/declared' do - declared(params) - end - - get '/declared?first=present&nested[][fourth]=1&nested[][fourth]=2' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested'].size).to eq 2 - end - - context 'sets nested objects when the param is missing' do - it 'to be a hash when include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end - - get '/declared?first=present' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']).to eq({}) - end - - it 'to be an array when include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end - - get '/declared?first=present' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['arr']).to be_a(Array) - end - - it 'to be an array when nested and include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end - - get '/declared?first=present&nested[fourth]=1' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']['nested_arr']).to be_a(Array) - end - - it 'to be nil when include_missing is false' do - subject.get '/declared' do - declared(params, include_missing: false) - end - - get '/declared?first=present' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']).to be_nil - end - end - - it 'filters out any additional params that are given' do - subject.get '/declared' do - declared(params) - end - get '/declared?first=one&other=two' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body).key?(:other)).to eq false - end - - it 'stringifies if that option is passed' do - subject.get '/declared' do - declared(params, stringify: true) - end - - get '/declared?first=one&other=two' - expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['first']).to eq 'one' - end - - it 'does not include missing attributes if that option is passed' do - subject.get '/declared' do - error! 'expected nil', 400 if declared(params, include_missing: false).key?(:second) - '' - end - - get '/declared?first=one&other=two' - expect(last_response.status).to eq(200) - end - - it 'does not include renamed missing attributes if that option is passed' do - subject.params do - optional :renamed_original, as: :renamed - end - subject.get '/declared' do - error! 'expected nil', 400 if declared(params, include_missing: false).key?(:renamed) - '' - end - - get '/declared?first=one&other=two' - expect(last_response.status).to eq(200) - end - - it 'includes attributes with value that evaluates to false' do - subject.params do - requires :first - optional :boolean - end - - subject.post '/declared' do - error!('expected false', 400) if declared(params, include_missing: false)[:boolean] != false - '' - end - - post '/declared', ::Grape::Json.dump(first: 'one', boolean: false), 'CONTENT_TYPE' => 'application/json' - expect(last_response.status).to eq(201) - end - - it 'includes attributes with value that evaluates to nil' do - subject.params do - requires :first - optional :second - end - - subject.post '/declared' do - error!('expected nil', 400) unless declared(params, include_missing: false)[:second].nil? - '' - end - - post '/declared', ::Grape::Json.dump(first: 'one', second: nil), 'CONTENT_TYPE' => 'application/json' - expect(last_response.status).to eq(201) - end - - it 'includes missing attributes with defaults when there are nested hashes' do - subject.get '/dummy' do - end - - subject.params do - requires :first - optional :second - optional :third, default: nil - optional :nested, type: Hash do - optional :fourth, default: nil - optional :fifth, default: nil - requires :nested_nested, type: Hash do - optional :sixth, default: 'sixth-default' - optional :seven, default: nil - end - end - end - - subject.get '/declared' do - declared(params, include_missing: false) - end - - get '/declared?first=present&nested[fourth]=&nested[nested_nested][sixth]=sixth' - json = JSON.parse(last_response.body) - expect(last_response.status).to eq(200) - expect(json['first']).to eq 'present' - expect(json['nested'].keys).to eq %w[fourth fifth nested_nested] - expect(json['nested']['fourth']).to eq '' - expect(json['nested']['nested_nested'].keys).to eq %w[sixth seven] - expect(json['nested']['nested_nested']['sixth']).to eq 'sixth' - end - - it 'does not include missing attributes when there are nested hashes' do - subject.get '/dummy' do - end - - subject.params do - requires :first - optional :second - optional :third - optional :nested, type: Hash do - optional :fourth - optional :fifth - end - end - - subject.get '/declared' do - declared(params, include_missing: false) - end - - get '/declared?first=present&nested[fourth]=4' - json = JSON.parse(last_response.body) - expect(last_response.status).to eq(200) - expect(json['first']).to eq 'present' - expect(json['nested'].keys).to eq %w[fourth] - expect(json['nested']['fourth']).to eq '4' - end - end - - describe '#declared; call from child namespace' do - before do - subject.format :json - subject.namespace :parent do - params do - requires :parent_name, type: String - end - - namespace ':parent_name' do - params do - requires :child_name, type: String - requires :child_age, type: Integer - end - - namespace ':child_name' do - params do - requires :grandchild_name, type: String - end - - get ':grandchild_name' do - { - 'params' => params, - 'without_parent_namespaces' => declared(params, include_parent_namespaces: false), - 'with_parent_namespaces' => declared(params, include_parent_namespaces: true) - } - end - end - end - end - - get '/parent/foo/bar/baz', child_age: 5, extra: 'hello' - end - - let(:parsed_response) { JSON.parse(last_response.body, symbolize_names: true) } - - it { expect(last_response.status).to eq 200 } - - context 'with include_parent_namespaces: false' do - it 'returns declared parameters only from current namespace' do - expect(parsed_response[:without_parent_namespaces]).to eq( - grandchild_name: 'baz' - ) - end - end - - context 'with include_parent_namespaces: true' do - it 'returns declared parameters from every parent namespace' do - expect(parsed_response[:with_parent_namespaces]).to eq( - parent_name: 'foo', - child_name: 'bar', - grandchild_name: 'baz', - child_age: 5 - ) - end - end - - context 'without declaration' do - it 'returns all requested parameters' do - expect(parsed_response[:params]).to eq( - parent_name: 'foo', - child_name: 'bar', - grandchild_name: 'baz', - child_age: 5, - extra: 'hello' - ) - end - end - end - - describe '#declared; from a nested mounted endpoint' do - before do - doubly_mounted = Class.new(Grape::API) - doubly_mounted.namespace :more do - params do - requires :y, type: Integer - end - route_param :y do - get do - { - params: params, - declared_params: declared(params) - } - end - end - end - - mounted = Class.new(Grape::API) - mounted.namespace :another do - params do - requires :mount_space, type: Integer - end - route_param :mount_space do - mount doubly_mounted - end - end - - subject.format :json - subject.namespace :something do - params do - requires :id, type: Integer - end - resource ':id' do - mount mounted - end - end - end - - it 'can access parent attributes' do - get '/something/123/another/456/more/789' - expect(last_response.status).to eq 200 - json = JSON.parse(last_response.body, symbolize_names: true) - - # test all three levels of params - expect(json[:declared_params][:y]).to eq 789 - expect(json[:declared_params][:mount_space]).to eq 456 - expect(json[:declared_params][:id]).to eq 123 - end - end - - describe '#declared; mixed nesting' do - before do - subject.format :json - subject.resource :users do - route_param :id, type: Integer, desc: 'ID desc' do - # Adding this causes route_setting(:declared_params) to be nil for the - # get block in namespace 'foo' below - get do - end - - namespace 'foo' do - get do - { - params: params, - declared_params: declared(params), - declared_params_no_parent: declared(params, include_parent_namespaces: false) - } - end - end - end - end - end - - it 'can access parent route_param' do - get '/users/123/foo', bar: 'bar' - expect(last_response.status).to eq 200 - json = JSON.parse(last_response.body, symbolize_names: true) - - expect(json[:declared_params][:id]).to eq 123 - expect(json[:declared_params_no_parent][:id]).to eq nil - end - end - - describe '#declared; with multiple route_param' do - before do - mounted = Class.new(Grape::API) - mounted.namespace :albums do - get do - declared(params) - end - end - - subject.format :json - subject.namespace :artists do - route_param :id, type: Integer do - get do - declared(params) - end - - params do - requires :filter, type: String - end - get :some_route do - declared(params) - end - end - - route_param :artist_id, type: Integer do - namespace :compositions do - get do - declared(params) - end - end - end - - route_param :compositor_id, type: Integer do - mount mounted - end - end - end - - it 'return only :id without :artist_id' do - get '/artists/1' - json = JSON.parse(last_response.body, symbolize_names: true) - - expect(json.key?(:id)).to be_truthy - expect(json.key?(:artist_id)).not_to be_truthy - end - - it 'return only :artist_id without :id' do - get '/artists/1/compositions' - json = JSON.parse(last_response.body, symbolize_names: true) - - expect(json.key?(:artist_id)).to be_truthy - expect(json.key?(:id)).not_to be_truthy - end - - it 'return :filter and :id parameters in declared for second enpoint inside route_param' do - get '/artists/1/some_route', filter: 'some_filter' - json = JSON.parse(last_response.body, symbolize_names: true) - - expect(json.key?(:filter)).to be_truthy - expect(json.key?(:id)).to be_truthy - expect(json.key?(:artist_id)).not_to be_truthy - end - - it 'return :compositor_id for mounter in route_param' do - get '/artists/1/albums' - json = JSON.parse(last_response.body, symbolize_names: true) - - expect(json.key?(:compositor_id)).to be_truthy - expect(json.key?(:id)).not_to be_truthy - expect(json.key?(:artist_id)).not_to be_truthy - end - end - describe '#params' do it 'is available to the caller' do subject.get('/hey') do From 678cd1366462c1a679a65833d9fb6f1fc0c87c72 Mon Sep 17 00:00:00 2001 From: Tim Connor Date: Fri, 18 Sep 2020 15:21:46 +1200 Subject: [PATCH 249/290] Ensure complete declared params structure is present --- CHANGELOG.md | 3 +- README.md | 55 ++++++++++++++++--- UPGRADING.md | 47 ++++++++++++++-- lib/grape/dsl/inside_route.rb | 59 ++++++++------------ lib/grape/version.rb | 2 +- spec/grape/endpoint/declared_spec.rb | 81 +++++++++++++++++++--------- 6 files changed, 174 insertions(+), 73 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08995a1a4..eaf05a116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.4.1 (Next) +### 1.5.0 (Next) #### Features @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2103](https://github.com/ruby-grape/grape/pull/2103): Ensure complete declared params structure is present - [@tlconnor](https://github.com/tlconnor). * [#2099](https://github.com/ruby-grape/grape/pull/2099): Added truffleruby to Travis-CI - [@gogainda](https://github.com/gogainda). * [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). * [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). diff --git a/README.md b/README.md index c3a5ce73b..43f35cbf0 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.4.1**. +You're reading the documentation for the next release of Grape, which should be **1.5.0**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. The current stable release is [1.4.0](https://github.com/ruby-grape/grape/blob/v1.4.0/README.md). @@ -353,7 +353,7 @@ use Rack::Session::Cookie run Rack::Cascade.new [Web, API] ``` -Note that order of loading apps using `Rack::Cascade` matters. The grape application must be last if you want to raise custom 404 errors from grape (such as `error!('Not Found',404)`). If the grape application is not last and returns 404 or 405 response, [cascade utilizes that as a signal to try the next app](https://www.rubydoc.info/gems/rack/Rack/Cascade). This may lead to undesirable behavior showing the [wrong 404 page from the wrong app](https://github.com/ruby-grape/grape/issues/1515). +Note that order of loading apps using `Rack::Cascade` matters. The grape application must be last if you want to raise custom 404 errors from grape (such as `error!('Not Found',404)`). If the grape application is not last and returns 404 or 405 response, [cascade utilizes that as a signal to try the next app](https://www.rubydoc.info/gems/rack/Rack/Cascade). This may lead to undesirable behavior showing the [wrong 404 page from the wrong app](https://github.com/ruby-grape/grape/issues/1515). ### Rails @@ -787,7 +787,12 @@ Available parameter builders are `Grape::Extensions::Hash::ParamBuilder`, `Grape ### Declared -Grape allows you to access only the parameters that have been declared by your `params` block. It filters out the params that have been passed, but are not allowed. Consider the following API endpoint: +Grape allows you to access only the parameters that have been declared by your `params` block. It will: + + * Filter out the params that have been passed, but are not allowed. + * Include any optional params that are declared but not passed. + +Consider the following API endpoint: ````ruby format :json @@ -820,9 +825,9 @@ Once we add parameters requirements, grape will start returning only the declare format :json params do - requires :user, type: Hash do - requires :first_name, type: String - requires :last_name, type: String + optional :user, type: Hash do + optional :first_name, type: String + optional :last_name, type: String end end @@ -850,6 +855,44 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d } ```` +Missing params that are declared as type `Hash` or `Array` will be included. + +````ruby +format :json + +params do + optional :user, type: Hash do + optional :first_name, type: String + optional :last_name, type: String + end + optional :widgets, type: Array +end + +post 'users/signup' do + { 'declared_params' => declared(params) } +end +```` + +**Request** + +````bash +curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d '{}' +```` + +**Response** + +````json +{ + "declared_params": { + "user": { + "first_name": null, + "last_name": null + }, + "widgets": [] + } +} +```` + The returned hash is an `ActiveSupport::HashWithIndifferentAccess`. The `#declared` method is not available to `before` filters, as those are evaluated prior to parameter coercion. diff --git a/UPGRADING.md b/UPGRADING.md index 591fa51ad..9d63392b9 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,45 @@ Upgrading Grape =============== +### Upgrading to >= 1.5.0 + +Prior to 1.3.3, the `declared` helper would always return the complete params structure if `include_missing=true` was set. In 1.3.3 a regression was introduced such that a missing Hash with or without nested parameters would always resolve to `{}`. + +In 1.5.0 this behavior is reverted, so the whole params structure will always be available via `declared`, regardless of whether any params are passed. + +The following rules now apply to the `declared` helper when params are missing and `include_missing=true`: + +* Hash params with children will resolve to a Hash with keys for each declared child. +* Hash params with no children will resolve to `{}`. +* Set params will resolve to `Set.new`. +* Array params will resolve to `[]`. +* All other params will resolve to `nil`. + +#### Example + +```ruby +class Api < Grape::API + params do + optional :outer, type: Hash do + optional :inner, type: Hash do + optional :value, type: String + end + end + end + get 'example' do + declared(params, include_missing: true) + end +end +``` + +``` +get '/example' +# 1.3.3 = {} +# 1.5.0 = {outer: {inner: {value:null}}} +``` + +For more information see [#2103](https://github.com/ruby-grape/grape/pull/2103). + ### Upgrading to >= 1.4.0 #### Reworking stream and file and un-deprecating stream like-objects @@ -28,17 +67,17 @@ class API < Grape::API end ``` -Or use `stream` to stream other kinds of content. In the following example a streamer class +Or use `stream` to stream other kinds of content. In the following example a streamer class streams paginated data from a database. ```ruby -class MyObject +class MyObject attr_accessor :result def initialize(query) @result = query end - + def each yield '[' # Do paginated DB fetches and return each page formatted @@ -47,7 +86,7 @@ class MyObject yield process_records(records, first) first = false end - yield ']' + yield ']' end def process_records(records, first) diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index dc8f9f05c..1eb0c3084 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -58,7 +58,7 @@ def declared_hash(passed_params, options, declared_params, params_nested_path) passed_children_params = passed_params[declared_parent_param] || passed_params.class.new memo_key = optioned_param_key(declared_parent_param, options) - memo[memo_key] = handle_passed_param(passed_children_params, params_nested_path_dup) do + memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do declared(passed_children_params, options, declared_children_params, params_nested_path_dup) end end @@ -70,57 +70,44 @@ def declared_hash(passed_params, options, declared_params, params_nested_path) next unless options[:include_missing] || passed_params.key?(declared_param) || (param_renaming && passed_params.key?(param_renaming)) - if param_renaming - memo[optioned_param_key(param_renaming, options)] = passed_params[param_renaming] - else - memo[optioned_param_key(declared_param, options)] = passed_params[declared_param] + memo_key = optioned_param_key(param_renaming || declared_param, options) + passed_param = passed_params[param_renaming || declared_param] + + params_nested_path_dup = params_nested_path.dup + params_nested_path_dup << declared_param.to_s + + memo[memo_key] = handle_passed_param(params_nested_path_dup) do + passed_param end end end end - def handle_passed_param(passed_children_params, params_nested_path, &_block) - if should_be_empty_hash?(passed_children_params, params_nested_path) + def handle_passed_param(params_nested_path, has_passed_children = false, &_block) + return yield if has_passed_children + + key = params_nested_path[0] + key += '[' + params_nested_path[1..-1].join('][') + ']' if params_nested_path.size > 1 + + route_options_params = options[:route_options][:params] || {} + type = route_options_params.dig(key, :type) + has_children = route_options_params.keys.any? { |k| k != key && k.start_with?(key) } + + if type == 'Hash' && !has_children {} - elsif should_be_empty_array?(passed_children_params, params_nested_path) + elsif type == 'Array' || type&.start_with?('[') [] + elsif type == 'Set' || type&.start_with?('# 1 - key - end - def optioned_declared_params(**options) declared_params = if options[:include_parent_namespaces] # Declared params including parent namespaces diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 615c251ab..05aefeb27 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.4.1' + VERSION = '1.5.0' end diff --git a/spec/grape/endpoint/declared_spec.rb b/spec/grape/endpoint/declared_spec.rb index 73d145a8e..cf332933b 100644 --- a/spec/grape/endpoint/declared_spec.rb +++ b/spec/grape/endpoint/declared_spec.rb @@ -28,10 +28,20 @@ def app optional :nested_arr, type: Array do optional :eighth end + optional :empty_arr, type: Array + optional :empty_typed_arr, type: Array[String] + optional :empty_hash, type: Hash + optional :empty_set, type: Set + optional :empty_typed_set, type: Set[String] end optional :arr, type: Array do optional :nineth end + optional :empty_arr, type: Array + optional :empty_typed_arr, type: Array[String] + optional :empty_hash, type: Hash + optional :empty_set, type: Set + optional :empty_typed_set, type: Set[String] end end @@ -103,7 +113,7 @@ def app end get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body).keys.size).to eq(5) + expect(JSON.parse(last_response.body).keys.size).to eq(10) end it 'has a optional param with default value all the time' do @@ -122,7 +132,7 @@ def app get '/declared?first=present&nested[fourth]=1' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 4 + expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 9 end it 'builds nested params when given array' do @@ -145,45 +155,66 @@ def app expect(JSON.parse(last_response.body)['nested'].size).to eq 2 end - context 'sets nested objects when the param is missing' do - it 'to be a hash when include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end + context 'when the param is missing and include_missing=false' do + before do + subject.get('/declared') { declared(params, include_missing: false) } + end + it 'sets nested objects to be nil' do get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']).to eq({}) + expect(JSON.parse(last_response.body)['nested']).to be_nil end + end - it 'to be an array when include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end + context 'when the param is missing and include_missing=true' do + before do + subject.get('/declared') { declared(params, include_missing: true) } + end + it 'sets objects with type=Hash to be a hash' do get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['arr']).to be_a(Array) - end - it 'to be an array when nested and include_missing is true' do - subject.get '/declared' do - declared(params, include_missing: true) - end + body = JSON.parse(last_response.body) + expect(body['empty_hash']).to eq({}) + expect(body['nested']).to be_a(Hash) + expect(body['nested']['empty_hash']).to eq({}) + expect(body['nested']['nested_two']).to be_a(Hash) + end - get '/declared?first=present&nested[fourth]=1' + it 'sets objects with type=Set to be a set' do + get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']['nested_arr']).to be_a(Array) + + body = JSON.parse(last_response.body) + expect(['#', []]).to include(body['empty_set']) + expect(['#', []]).to include(body['empty_typed_set']) + expect(['#', []]).to include(body['nested']['empty_set']) + expect(['#', []]).to include(body['nested']['empty_typed_set']) end - it 'to be nil when include_missing is false' do - subject.get '/declared' do - declared(params, include_missing: false) - end + it 'sets objects with type=Array to be an array' do + get '/declared?first=present' + expect(last_response.status).to eq(200) + + body = JSON.parse(last_response.body) + expect(body['empty_arr']).to eq([]) + expect(body['empty_typed_arr']).to eq([]) + expect(body['arr']).to eq([]) + expect(body['nested']['empty_arr']).to eq([]) + expect(body['nested']['empty_typed_arr']).to eq([]) + expect(body['nested']['nested_arr']).to eq([]) + end + it 'includes all declared children when type=Hash' do get '/declared?first=present' expect(last_response.status).to eq(200) - expect(JSON.parse(last_response.body)['nested']).to be_nil + + body = JSON.parse(last_response.body) + expect(body['nested'].keys).to eq(%w[fourth fifth nested_two nested_arr empty_arr empty_typed_arr empty_hash empty_set empty_typed_set]) + expect(body['nested']['nested_two'].keys).to eq(%w[sixth nested_three]) + expect(body['nested']['nested_two']['nested_three'].keys).to eq(%w[seventh]) end end From 9b678f44ad167f3192ee2c89c7da81b44cc3fd09 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 30 Sep 2020 08:46:52 -0700 Subject: [PATCH 250/290] Fix Ruby 2.7 deprecation warning This fixes the warning: ``` ruby/2.7.0/gems/grape-1.4.0/lib/grape/dsl/inside_route.rb: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call ``` More details: https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ --- CHANGELOG.md | 1 + lib/grape/dsl/inside_route.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf05a116..e50ff9355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2104](https://github.com/ruby-grape/grape/pull/2104): Fix Ruby 2.7 keyword deprecation warning - [@stanhu](https://github.com/stanhu). * [#2103](https://github.com/ruby-grape/grape/pull/2103): Ensure complete declared params structure is present - [@tlconnor](https://github.com/tlconnor). * [#2099](https://github.com/ruby-grape/grape/pull/2099): Added truffleruby to Travis-CI - [@gogainda](https://github.com/gogainda). * [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 1eb0c3084..35046e803 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -422,7 +422,7 @@ def entity_class_for_obj(object, options) def entity_representation_for(entity_class, object, options) embeds = { env: env } embeds[:version] = env[Grape::Env::API_VERSION] if env[Grape::Env::API_VERSION] - entity_class.represent(object, embeds.merge(options)) + entity_class.represent(object, **embeds.merge(options)) end end end From 6d937c9f89a29d5c3e4725a63d9d0d26b73735fc Mon Sep 17 00:00:00 2001 From: dblock Date: Wed, 30 Sep 2020 12:55:25 -0400 Subject: [PATCH 251/290] Enable new cops for RuboCop. --- .rubocop.yml | 7 +-- .rubocop_todo.yml | 49 +++++-------------- Gemfile | 2 +- Rakefile | 16 +++--- benchmark/remounting.rb | 2 + lib/grape/dsl/helpers.rb | 1 + lib/grape/middleware/base.rb | 1 + lib/grape/util/lazy_value.rb | 1 + spec/grape/entity_spec.rb | 6 +++ spec/grape/middleware/stack_spec.rb | 1 + .../validators/except_values_spec.rb | 1 + 11 files changed, 37 insertions(+), 50 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index f2d32fbfd..e03c5ca66 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,12 +1,13 @@ -require: - - rubocop-performance - AllCops: + NewCops: enable TargetRubyVersion: 2.4 Exclude: - vendor/**/* - bin/**/* +require: + - rubocop-performance + inherit_from: .rubocop_todo.yml Style/Documentation: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 79cfdde38..84b9fc45b 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-05-26 08:28:37 -0400 using RuboCop version 0.84.0. +# on 2020-09-30 12:54:06 -0400 using RuboCop version 0.84.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -13,7 +13,7 @@ Layout/ClosingHeredocIndentation: - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' -# Offense count: 71 +# Offense count: 73 # Cop supports --auto-correct. Layout/EmptyLineAfterGuardClause: Enabled: false @@ -53,22 +53,16 @@ Lint/NonDeterministicRequireOrder: Exclude: - 'spec/spec_helper.rb' -# Offense count: 1 -# Cop supports --auto-correct. -Lint/RedundantCopDisableDirective: - Exclude: - - 'lib/grape/router/attribute_translator.rb' - # Offense count: 2 # Cop supports --auto-correct. Lint/ToJSON: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 47 +# Offense count: 50 # Configuration parameters: IgnoredMethods. Metrics/AbcSize: - Max: 44 + Max: 43 # Offense count: 6 # Configuration parameters: CountComments, ExcludedMethods. @@ -76,20 +70,20 @@ Metrics/AbcSize: Metrics/BlockLength: Max: 182 -# Offense count: 10 +# Offense count: 11 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 305 + Max: 304 # Offense count: 30 # Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: Max: 14 -# Offense count: 61 +# Offense count: 69 # Configuration parameters: CountComments, ExcludedMethods. Metrics/MethodLength: - Max: 36 + Max: 32 # Offense count: 12 # Configuration parameters: CountComments. @@ -120,13 +114,6 @@ Naming/MethodParameterName: - 'lib/grape/middleware/stack.rb' - 'spec/grape/api_spec.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: PreferredName. -Naming/RescuedExceptionsVariableName: - Exclude: - - 'lib/grape/middleware/error.rb' - # Offense count: 3 # Cop supports --auto-correct. Performance/InefficientHashSearch: @@ -157,25 +144,20 @@ Style/ExpandPathArguments: Style/FormatStringToken: EnforcedStyle: template -# Offense count: 23 +# Offense count: 19 # Cop supports --auto-correct. Style/IfUnlessModifier: Exclude: - 'lib/grape/api/instance.rb' - 'lib/grape/dsl/desc.rb' - 'lib/grape/dsl/request_response.rb' - - 'lib/grape/dsl/routing.rb' - 'lib/grape/dsl/settings.rb' - 'lib/grape/endpoint.rb' - 'lib/grape/error_formatter/json.rb' - 'lib/grape/error_formatter/xml.rb' - - 'lib/grape/middleware/error.rb' - 'lib/grape/middleware/formatter.rb' - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/validations/params_scope.rb' - - 'lib/grape/validations/validators/base.rb' - - 'lib/grape/validations/validators/default.rb' - - 'spec/support/versioned_helpers.rb' # Offense count: 1 Style/MethodMissingSuper: @@ -191,7 +173,7 @@ Style/NumericPredicate: - 'spec/**/*' - 'lib/grape/middleware/formatter.rb' -# Offense count: 11 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. # AllowedMethods: present?, blank?, presence, try, try! @@ -202,18 +184,9 @@ Style/SafeNavigation: - 'lib/grape/dsl/inside_route.rb' - 'lib/grape/dsl/request_response.rb' - 'lib/grape/endpoint.rb' - - 'lib/grape/middleware/error.rb' - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/middleware/versioner/header.rb' -# Offense count: 2 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInHashLiteral: - Exclude: - - 'lib/grape/middleware/error.rb' - # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinSize, WordRegex. @@ -223,7 +196,7 @@ Style/WordArray: - 'spec/grape/validations/validators/except_values_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 125 +# Offense count: 131 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/Gemfile b/Gemfile index 6c8f23291..99de12cfb 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ # when changing this file, run appraisal install ; rubocop -a gemfiles/*.gemfile -source 'https://rubygems.org' +source('https://rubygems.org') gemspec diff --git a/Rakefile b/Rakefile index 58788aa82..2d79797f7 100644 --- a/Rakefile +++ b/Rakefile @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'rubygems' -require 'bundler' -Bundler.setup :default, :test, :development +require('rubygems') +require('bundler') +Bundler.setup(:default, :test, :development) Bundler::GemHelper.install_tasks -require 'rspec/core/rake_task' +require('rspec/core/rake_task') RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/**/*_spec.rb' spec.exclude_pattern = 'spec/integration/**/*_spec.rb' @@ -17,11 +17,11 @@ RSpec::Core::RakeTask.new(:rcov) do |spec| spec.rcov = true end -task :spec +task(:spec) -require 'rainbow/ext/string' unless String.respond_to?(:color) +require('rainbow/ext/string') unless String.respond_to?(:color) -require 'rubocop/rake_task' +require('rubocop/rake_task') RuboCop::RakeTask.new -task default: %i[rubocop spec] +task(default: %i[rubocop spec]) diff --git a/benchmark/remounting.rb b/benchmark/remounting.rb index a585c1d2e..5c565b1d3 100644 --- a/benchmark/remounting.rb +++ b/benchmark/remounting.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'grape' require 'benchmark/memory' diff --git a/lib/grape/dsl/helpers.rb b/lib/grape/dsl/helpers.rb index bbd2ed3ba..d461b34e3 100644 --- a/lib/grape/dsl/helpers.rb +++ b/lib/grape/dsl/helpers.rb @@ -81,6 +81,7 @@ def inject_api_helpers_to_mod(mod, &_block) # to provide some API-specific functionality. module BaseHelper attr_accessor :api + def params(name, &block) @named_params ||= {} @named_params[name] = block diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index e21a94e9e..0e0f1729c 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -8,6 +8,7 @@ class Base include Helpers attr_reader :app, :env, :options + TEXT_HTML = 'text/html' include Grape::DSL::Headers diff --git a/lib/grape/util/lazy_value.rb b/lib/grape/util/lazy_value.rb index b11364538..d757ad3e2 100644 --- a/lib/grape/util/lazy_value.rb +++ b/lib/grape/util/lazy_value.rb @@ -4,6 +4,7 @@ module Grape module Util class LazyValue attr_reader :access_keys + def initialize(value, access_keys = []) @value = value @access_keys = access_keys diff --git a/spec/grape/entity_spec.rb b/spec/grape/entity_spec.rb index 08c378ef2..add8461be 100644 --- a/spec/grape/entity_spec.rb +++ b/spec/grape/entity_spec.rb @@ -181,6 +181,7 @@ def first subject.get '/example' do c = Class.new do attr_reader :id + def initialize(id) @id = id end @@ -202,6 +203,7 @@ def initialize(id) subject.get '/examples' do c = Class.new do attr_reader :id + def initialize(id) @id = id end @@ -226,6 +228,7 @@ def initialize(id) subject.get '/example' do c = Class.new do attr_reader :name + def initialize(args) @name = args[:name] || 'no name set' end @@ -255,6 +258,7 @@ def initialize(args) subject.get '/example' do c = Class.new do attr_reader :name + def initialize(args) @name = args[:name] || 'no name set' end @@ -284,6 +288,7 @@ def initialize(args) subject.get '/example' do c = Class.new do attr_reader :name + def initialize(args) @name = args[:name] || 'no name set' end @@ -302,6 +307,7 @@ def initialize(args) it 'present with multiple entities using optional symbol' do user = Class.new do attr_reader :name + def initialize(args) @name = args[:name] || 'no name set' end diff --git a/spec/grape/middleware/stack_spec.rb b/spec/grape/middleware/stack_spec.rb index 3833337e4..64f9bf382 100644 --- a/spec/grape/middleware/stack_spec.rb +++ b/spec/grape/middleware/stack_spec.rb @@ -8,6 +8,7 @@ class FooMiddleware; end class BarMiddleware; end class BlockMiddleware attr_reader :block + def initialize(&block) @block = block end diff --git a/spec/grape/validations/validators/except_values_spec.rb b/spec/grape/validations/validators/except_values_spec.rb index 1bdbfc805..4757cff8a 100644 --- a/spec/grape/validations/validators/except_values_spec.rb +++ b/spec/grape/validations/validators/except_values_spec.rb @@ -8,6 +8,7 @@ class ExceptValuesModel DEFAULT_EXCEPTS = ['invalid-type1', 'invalid-type2', 'invalid-type3'].freeze class << self attr_accessor :excepts + def excepts @excepts ||= [] [DEFAULT_EXCEPTS + @excepts].flatten.uniq From b8ebd82f418e8b370aed84b5a856ea8875d687f8 Mon Sep 17 00:00:00 2001 From: dblock Date: Wed, 30 Sep 2020 14:03:04 -0400 Subject: [PATCH 252/290] Move read_chunks into a support helper file. --- spec/spec_helper.rb | 10 ---------- spec/support/chunks.rb | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 spec/support/chunks.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index db3cf8b7b..d0bb66554 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,17 +20,7 @@ # so it should be set to true here as well to reflect that. I18n.enforce_available_locales = true -module Chunks - def read_chunks(body) - buffer = [] - body.each { |chunk| buffer << chunk } - - buffer - end -end - RSpec.configure do |config| - config.include Chunks config.include Rack::Test::Methods config.include Spec::Support::Helpers config.raise_errors_for_deprecations! diff --git a/spec/support/chunks.rb b/spec/support/chunks.rb new file mode 100644 index 000000000..0506cb7ce --- /dev/null +++ b/spec/support/chunks.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Chunks + def read_chunks(body) + buffer = [] + body.each { |chunk| buffer << chunk } + + buffer + end +end + +RSpec.configure do |config| + config.include Chunks +end From 028c10e9d4908d301667a18658f83c28f891ee5d Mon Sep 17 00:00:00 2001 From: Tim Connor Date: Thu, 1 Oct 2020 18:37:53 +1300 Subject: [PATCH 253/290] Fix bug with handling array params. Fixes an issue introduced in 7e432153f08d4985b0df625d11aef28a38beb4fe that results in Array leaf params being ignored. For example, given the following API and Request: ``` class Api < Grape::API params do optional :array, type: Array end post 'example' do declared(params, include_missing: true) end end POST /example { "array": ["value"] } ``` Expected Response Body: ``` { "array": ["value"] } ``` Observed Response Body: ``` { "array": [] } ``` --- lib/grape/dsl/inside_route.rb | 3 +-- spec/grape/endpoint/declared_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 1eb0c3084..828414bd7 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -75,8 +75,7 @@ def declared_hash(passed_params, options, declared_params, params_nested_path) params_nested_path_dup = params_nested_path.dup params_nested_path_dup << declared_param.to_s - - memo[memo_key] = handle_passed_param(params_nested_path_dup) do + memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do passed_param end end diff --git a/spec/grape/endpoint/declared_spec.rb b/spec/grape/endpoint/declared_spec.rb index cf332933b..860f1cce4 100644 --- a/spec/grape/endpoint/declared_spec.rb +++ b/spec/grape/endpoint/declared_spec.rb @@ -135,6 +135,20 @@ def app expect(JSON.parse(last_response.body)['nested'].keys.size).to eq 9 end + it 'builds arrays correctly' do + subject.params do + requires :first + optional :second, type: Array + end + subject.post('/declared') { declared(params) } + + post '/declared', first: 'present', second: ['present'] + expect(last_response.status).to eq(201) + + body = JSON.parse(last_response.body) + expect(body['second']).to eq(['present']) + end + it 'builds nested params when given array' do subject.get '/dummy' do end From b82040fb177dc28e0b8761b474c58209bf5bbe11 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 5 Oct 2020 08:24:43 -0400 Subject: [PATCH 254/290] Preparing for release, 1.5.0. --- CHANGELOG.md | 7 +------ README.md | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e50ff9355..dbaae37a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,7 @@ -### 1.5.0 (Next) - -#### Features - -* Your contribution here. +### 1.5.0 (2020/10/05) #### Fixes -* Your contribution here. * [#2104](https://github.com/ruby-grape/grape/pull/2104): Fix Ruby 2.7 keyword deprecation warning - [@stanhu](https://github.com/stanhu). * [#2103](https://github.com/ruby-grape/grape/pull/2103): Ensure complete declared params structure is present - [@tlconnor](https://github.com/tlconnor). * [#2099](https://github.com/ruby-grape/grape/pull/2099): Added truffleruby to Travis-CI - [@gogainda](https://github.com/gogainda). diff --git a/README.md b/README.md index 43f35cbf0..4d1f25e42 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,8 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.5.0**. +You're reading the documentation for the stable release of Grape, 1.5.0. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.4.0](https://github.com/ruby-grape/grape/blob/v1.4.0/README.md). ## Project Resources From 02d7113d09eb9fcb4264c841d1fdd305e3e8adb5 Mon Sep 17 00:00:00 2001 From: dblock Date: Mon, 5 Oct 2020 08:26:01 -0400 Subject: [PATCH 255/290] Preparing for next developer iteration, 1.5.1. --- CHANGELOG.md | 10 ++++++++++ README.md | 3 ++- lib/grape/version.rb | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbaae37a6..71a7cde38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.5.1 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.5.0 (2020/10/05) #### Fixes diff --git a/README.md b/README.md index 4d1f25e42..f68d58bcb 100644 --- a/README.md +++ b/README.md @@ -156,8 +156,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, 1.5.0. +You're reading the documentation for the next release of Grape, which should be **1.5.1**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.5.0](https://github.com/ruby-grape/grape/blob/v1.5.0/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 05aefeb27..5fd4319c1 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.5.0' + VERSION = '1.5.1' end From 44217ddef12ca53d930f46b80601ae40dd5cb82f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 9 Oct 2020 09:52:52 -0700 Subject: [PATCH 256/290] Fix declared_params regression with multiple allowed types Prior to Grape v1.5.0 and https://github.com/ruby-grape/grape/pull/2103, the following would return `nil`: ``` params do optional :status_code, types: [Integer, String] end get '/' do declared_params end ``` However, now it turns an empty `Array`. We restore the previous behavior by not returning an empty `Array` if multiple types are used. Closes https://github.com/ruby-grape/grape/issues/2115 --- CHANGELOG.md | 1 + lib/grape/dsl/inside_route.rb | 2 +- spec/grape/endpoint/declared_spec.rb | 13 ++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71a7cde38..ebddceccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2115](https://github.com/ruby-grape/grape/pull/2115): Fix declared_params regression with multiple allowed types - [@stanhu](https://github.com/stanhu). ### 1.5.0 (2020/10/05) diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 6f4becfea..134f1ea24 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -94,7 +94,7 @@ def handle_passed_param(params_nested_path, has_passed_children = false, &_block if type == 'Hash' && !has_children {} - elsif type == 'Array' || type&.start_with?('[') + elsif type == 'Array' || type&.start_with?('[') && !type&.include?(',') [] elsif type == 'Set' || type&.start_with?('# Date: Fri, 16 Oct 2020 12:41:51 +0200 Subject: [PATCH 257/290] Update README.md Fixed missing 's' --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f68d58bcb..3ad038cf4 100644 --- a/README.md +++ b/README.md @@ -2040,10 +2040,10 @@ end # is NOT the same as -get ':status' do # this makes param[:status] available +get ':status' do # this makes params[:status] available end -# This will make both param[:status_id] and param[:id] available +# This will make both params[:status_id] and params[:id] available get 'statuses/:status_id/reviews/:id' do end From 552e01c059e6a751d6e620a936a85e6d51ddf2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Edstr=C3=B6m?= <108799+Legogris@users.noreply.github.com> Date: Sat, 17 Oct 2020 08:51:16 +0900 Subject: [PATCH 258/290] Fix 2.7 deprecation warning in middleware/stack (#2123) --- CHANGELOG.md | 1 + lib/grape/middleware/stack.rb | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebddceccb..76c167eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Your contribution here. * [#2115](https://github.com/ruby-grape/grape/pull/2115): Fix declared_params regression with multiple allowed types - [@stanhu](https://github.com/stanhu). +* [#2123](https://github.com/ruby-grape/grape/pull/2123): Fix 2.7 deprecation warning in middleware/stack - [@Legogris](https://github.com/Legogris). ### 1.5.0 (2020/10/05) diff --git a/lib/grape/middleware/stack.rb b/lib/grape/middleware/stack.rb index b725e8787..a11869da8 100644 --- a/lib/grape/middleware/stack.rb +++ b/lib/grape/middleware/stack.rb @@ -70,9 +70,9 @@ def [](i) middlewares[i] end - def insert(index, *args, &block) + def insert(index, *args, **kwargs, &block) index = assert_index(index, :before) - middleware = self.class::Middleware.new(*args, &block) + middleware = self.class::Middleware.new(*args, **kwargs, &block) middlewares.insert(index, middleware) end @@ -83,8 +83,8 @@ def insert_after(index, *args, &block) insert(index + 1, *args, &block) end - def use(*args, &block) - middleware = self.class::Middleware.new(*args, &block) + def use(*args, **kwargs, &block) + middleware = self.class::Middleware.new(*args, **kwargs, &block) middlewares.push(middleware) end From 4753cb3f0015823a9a4ccacb1abdbb29bbd0b256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Edstr=C3=B6m?= <108799+Legogris@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:39:43 +0900 Subject: [PATCH 259/290] Fix 2.7 deprecation warning in validator_factory (#2121) --- CHANGELOG.md | 1 + lib/grape/validations/validator_factory.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c167eef..3d43e98f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2121](https://github.com/ruby-grape/grape/pull/2121): Fix 2.7 deprecation warning in validator_factory - [@Legogris](https://github.com/Legogris). * [#2115](https://github.com/ruby-grape/grape/pull/2115): Fix declared_params regression with multiple allowed types - [@stanhu](https://github.com/stanhu). * [#2123](https://github.com/ruby-grape/grape/pull/2123): Fix 2.7 deprecation warning in middleware/stack - [@Legogris](https://github.com/Legogris). diff --git a/lib/grape/validations/validator_factory.rb b/lib/grape/validations/validator_factory.rb index f23655f10..444fa0421 100644 --- a/lib/grape/validations/validator_factory.rb +++ b/lib/grape/validations/validator_factory.rb @@ -8,7 +8,7 @@ def self.create_validator(**options) options[:options], options[:required], options[:params_scope], - options[:opts]) + **options[:opts]) end end end From 5d20ed89d22377aa16fc26f9520ebaf70a1fdd4c Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 25 Oct 2020 13:33:38 -0700 Subject: [PATCH 260/290] Remove redundant attributes on AttributeTranslator This fixes warnings about redefined attribute readers for #requirements and #request_method. --- CHANGELOG.md | 1 + lib/grape/router/attribute_translator.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d43e98f4..75f599fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2126](https://github.com/ruby-grape/grape/pull/2126): Fix warnings about redefined attribute accessors in `AttributeTranslator` - [@samsonjs](https://github.com/samsonjs). * [#2121](https://github.com/ruby-grape/grape/pull/2121): Fix 2.7 deprecation warning in validator_factory - [@Legogris](https://github.com/Legogris). * [#2115](https://github.com/ruby-grape/grape/pull/2115): Fix declared_params regression with multiple allowed types - [@stanhu](https://github.com/stanhu). * [#2123](https://github.com/ruby-grape/grape/pull/2123): Fix 2.7 deprecation warning in middleware/stack - [@Legogris](https://github.com/Legogris). diff --git a/lib/grape/router/attribute_translator.rb b/lib/grape/router/attribute_translator.rb index 88003887c..93ba4bdcd 100644 --- a/lib/grape/router/attribute_translator.rb +++ b/lib/grape/router/attribute_translator.rb @@ -4,7 +4,7 @@ module Grape class Router # this could be an OpenStruct, but doesn't work in Ruby 2.3.0, see https://bugs.ruby-lang.org/issues/12251 class AttributeTranslator - attr_reader :attributes, :request_method, :requirements + attr_reader :attributes ROUTE_ATTRIBUTES = %i[ prefix From affd474311fe9cb4dbbcb958742b73cb27037ffb Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Tue, 27 Oct 2020 08:48:00 +0200 Subject: [PATCH 261/290] fix a performance issue with dependent params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/ruby-grape/grape/issues/2100 The reason was in `ActiveSupport::HashWithIndifferentAccess`, it is super expensive. When users use a `Grape::Extensions::ActiveSupport::HashWithIndifferentAccess::ParamBuilder` or `Grape::Extensions::Hashie::Mash::ParamBuilder` parameter builder there is no change. However, users who use `Grape::Extensions::Hash::ParamBuilder` must make sure a parameter to be dependent on must be a symbol. given :matrix do # block here end Benchmark after this fix: Warming up -------------------------------------- Given 1.000 i/100ms Simple 1.000 i/100ms Calculating ------------------------------------- Given 0.804 (± 0.0%) i/s - 49.000 in 61.186831s Simple 0.855 (± 0.0%) i/s - 52.000 in 60.926097s Comparison: Simple: 0.9 i/s Given: 0.8 i/s - 1.06x slower --- CHANGELOG.md | 1 + UPGRADING.md | 22 ++++++++++++++++++++++ lib/grape/validations/params_scope.rb | 5 +++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f599fb3..4aefdb27d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2127](https://github.com/ruby-grape/grape/pull/2127): Fix a performance issue with dependent params - [@dnesteryuk](https://github.com/dnesteryuk). * [#2126](https://github.com/ruby-grape/grape/pull/2126): Fix warnings about redefined attribute accessors in `AttributeTranslator` - [@samsonjs](https://github.com/samsonjs). * [#2121](https://github.com/ruby-grape/grape/pull/2121): Fix 2.7 deprecation warning in validator_factory - [@Legogris](https://github.com/Legogris). * [#2115](https://github.com/ruby-grape/grape/pull/2115): Fix declared_params regression with multiple allowed types - [@stanhu](https://github.com/stanhu). diff --git a/UPGRADING.md b/UPGRADING.md index 9d63392b9..e5b6947fa 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,28 @@ Upgrading Grape =============== +### Upgrading to >= 1.5.1 + +#### Dependent params + +If you use [dependent params](https://github.com/ruby-grape/grape#dependent-parameters) with +`Grape::Extensions::Hash::ParamBuilder`, make sure a parameter to be dependent on is set as a Symbol. +If a String is given, a parameter that other parameters depend on won't be found even if it is present. + +_Correct_: +```ruby +given :matrix do + # dependent params +end +``` + +_Wrong_: +```ruby +given 'matrix' do + # dependent params +end +``` + ### Upgrading to >= 1.5.0 Prior to 1.3.3, the `declared` helper would always return the complete params structure if `include_missing=true` was set. In 1.3.3 a regression was introduced such that a missing Hash with or without nested parameters would always resolve to `{}`. diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index a51be6e1e..792762b06 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -61,8 +61,9 @@ def meets_dependency?(params, request_params) end return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array) - return false unless params.respond_to?(:with_indifferent_access) - params = params.with_indifferent_access + + # params might be anything what looks like a hash, so it must implement a `key?` method + return false unless params.respond_to?(:key?) @dependent_on.each do |dependency| if dependency.is_a?(Hash) From 7637b2724f69c1ec24c974d4993054e9bdcc5f7e Mon Sep 17 00:00:00 2001 From: David Henry Date: Wed, 4 Nov 2020 18:20:01 +0000 Subject: [PATCH 262/290] Fix validation error for nested array when `requires` => `optional` => `requires` This bug is due to the params parsing code return `{}` when the value is not found. This is fine in most cases, however in the instance that the value was optional and has nested required values. I was not able to find a way to confind the changes to just the parameter parsing as this resulted in the indexing being incorrect if any of the other array objects had an error. I have used a class to indicate that an Optional Value was missing as this avoid issues with the value actually being in the response. --- CHANGELOG.md | 1 + lib/grape/dsl/parameters.rb | 10 +++++-- .../validations/single_attribute_iterator.rb | 12 +++++++- lib/grape/validations/validators/base.rb | 3 +- .../single_attribute_iterator_spec.rb | 23 +++++++++++---- spec/grape/validations_spec.rb | 28 +++++++++++++++++++ 6 files changed, 66 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aefdb27d..90ae03275 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2128](https://github.com/ruby-grape/grape/pull/2128): Fix validation error when Required Array nested inside an optional array - [@dwhenry](https://github.com/dwhenry). * [#2127](https://github.com/ruby-grape/grape/pull/2127): Fix a performance issue with dependent params - [@dnesteryuk](https://github.com/dnesteryuk). * [#2126](https://github.com/ruby-grape/grape/pull/2126): Fix warnings about redefined attribute accessors in `AttributeTranslator` - [@samsonjs](https://github.com/samsonjs). * [#2121](https://github.com/ruby-grape/grape/pull/2121): Fix 2.7 deprecation warning in validator_factory - [@Legogris](https://github.com/Legogris). diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index 9d393fd93..b448ca52a 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -227,13 +227,17 @@ def declared_param?(param) alias group requires - def map_params(params, element) + class EmptyOptionalValue; end + + def map_params(params, element, is_array = false) if params.is_a?(Array) params.map do |el| - map_params(el, element) + map_params(el, element, true) end elsif params.is_a?(Hash) - params[element] || {} + params[element] || (@optional && is_array ? EmptyOptionalValue : {}) + elsif params == EmptyOptionalValue + EmptyOptionalValue else {} end diff --git a/lib/grape/validations/single_attribute_iterator.rb b/lib/grape/validations/single_attribute_iterator.rb index f28159896..bbfad45ac 100644 --- a/lib/grape/validations/single_attribute_iterator.rb +++ b/lib/grape/validations/single_attribute_iterator.rb @@ -7,10 +7,20 @@ class SingleAttributeIterator < AttributesIterator def yield_attributes(val, attrs) attrs.each do |attr_name| - yield val, attr_name, empty?(val) + yield val, attr_name, empty?(val), skip?(val) end end + + # This is a special case so that we can ignore tree's where option + # values are missing lower down. Unfortunately we can remove this + # are the parameter parsing stage as they are required to ensure + # the correct indexing is maintained + def skip?(val) + # return false + val == Grape::DSL::Parameters::EmptyOptionalValue + end + # Primitives like Integers and Booleans don't respond to +empty?+. # It could be possible to use +blank?+ instead, but # diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 4799f4923..af7030f14 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -43,7 +43,8 @@ def validate!(params) # there may be more than one error per field array_errors = [] - attributes.each do |val, attr_name, empty_val| + attributes.each do |val, attr_name, empty_val, skip_value| + next if skip_value next if !@scope.required? && empty_val next unless @scope.meets_dependency?(val, params) begin diff --git a/spec/grape/validations/single_attribute_iterator_spec.rb b/spec/grape/validations/single_attribute_iterator_spec.rb index 2b3edbf06..31a51dc47 100644 --- a/spec/grape/validations/single_attribute_iterator_spec.rb +++ b/spec/grape/validations/single_attribute_iterator_spec.rb @@ -15,7 +15,7 @@ it 'yields params and every single attribute from the list' do expect { |b| iterator.each(&b) } - .to yield_successive_args([params, :first, false], [params, :second, false]) + .to yield_successive_args([params, :first, false, false], [params, :second, false, false]) end end @@ -26,8 +26,8 @@ it 'yields every single attribute from the list for each of the array elements' do expect { |b| iterator.each(&b) }.to yield_successive_args( - [params[0], :first, false], [params[0], :second, false], - [params[1], :first, false], [params[1], :second, false] + [params[0], :first, false, false], [params[0], :second, false, false], + [params[1], :first, false, false], [params[1], :second, false, false] ) end @@ -36,9 +36,20 @@ it 'marks params with empty values' do expect { |b| iterator.each(&b) }.to yield_successive_args( - [params[0], :first, true], [params[0], :second, true], - [params[1], :first, true], [params[1], :second, true], - [params[2], :first, false], [params[2], :second, false] + [params[0], :first, true, false], [params[0], :second, true, false], + [params[1], :first, true, false], [params[1], :second, true, false], + [params[2], :first, false, false], [params[2], :second, false, false] + ) + end + end + + context 'when missing optional value' do + let(:params) { [Grape::DSL::Parameters::EmptyOptionalValue, 10] } + + it 'marks params with skipped values' do + expect { |b| iterator.each(&b) }.to yield_successive_args( + [params[0], :first, false, true], [params[0], :second, false, true], + [params[1], :first, false, false], [params[1], :second, false, false], ) end end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 232c23534..a9d2cd18e 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -883,6 +883,34 @@ def validate_param!(attr_name, params) end expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]]) end + + it "does not report errors when required array inside missing optional array" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Array do + requires :batches, type: Array do + requires :batch_no, type: String + end + end + end + end + + subject.get '/validate_required_arrays_under_optional_arrays' do + 'validate_required_arrays_under_optional_arrays works!' + end + + data = { + orders: [ + { id: 77, drugs: [{batches: [{batch_no: "A1234567"}]}]}, + { id: 70 } + ] + } + + get '/validate_required_arrays_under_optional_arrays', data + expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!") + expect(last_response.status).to eq(200) + end end context 'multiple validation errors' do From 2647e2cf76383a750c11d95b006499a378d6ba2f Mon Sep 17 00:00:00 2001 From: David Henry Date: Thu, 5 Nov 2020 16:33:32 +0000 Subject: [PATCH 263/290] Increase test coverage around nexted arrays/hashes with optional components This adds a number of tests around edge cases and different structures that could result in the previous validation incorrectly reporting missing data as a result of an optional element not being present. --- .../validations/single_attribute_iterator.rb | 1 - spec/grape/validations_spec.rb | 190 ++++++++++++++++-- 2 files changed, 173 insertions(+), 18 deletions(-) diff --git a/lib/grape/validations/single_attribute_iterator.rb b/lib/grape/validations/single_attribute_iterator.rb index bbfad45ac..639b03957 100644 --- a/lib/grape/validations/single_attribute_iterator.rb +++ b/lib/grape/validations/single_attribute_iterator.rb @@ -17,7 +17,6 @@ def yield_attributes(val, attrs) # are the parameter parsing stage as they are required to ensure # the correct indexing is maintained def skip?(val) - # return false val == Grape::DSL::Parameters::EmptyOptionalValue end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index a9d2cd18e..5f71a330e 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -884,32 +884,188 @@ def validate_param!(attr_name, params) expect(declared_params).to eq([items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]]) end - it "does not report errors when required array inside missing optional array" do - subject.params do - requires :orders, type: Array do - requires :id, type: Integer - optional :drugs, type: Array do - requires :batches, type: Array do + context <<~DESC do + Issue occurs whenever: + * param structure with at least three levels + * 1st level item is a required Array that has >1 entry with an optional item present and >1 entry with an optional item missing + * 2nd level is an optional Array or Hash + * 3rd level is a required item (can be any type) + * additional levels do not effect the issue from occuring + DESC + + it "example based off actual real world use case" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Array do + requires :batches, type: Array do + requires :batch_no, type: String + end + end + end + end + + subject.get '/validate_required_arrays_under_optional_arrays' do + 'validate_required_arrays_under_optional_arrays works!' + end + + data = { + orders: [ + { id: 77, drugs: [{batches: [{batch_no: "A1234567"}]}]}, + { id: 70 } + ] + } + + get '/validate_required_arrays_under_optional_arrays', data + expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!") + expect(last_response.status).to eq(200) + end + + it "simplest example using Arry -> Array -> Hash -> String" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Array do + requires :batch_no, type: String + end + end + end + + subject.get '/validate_required_arrays_under_optional_arrays' do + 'validate_required_arrays_under_optional_arrays works!' + end + + data = { + orders: [ + { id: 77, drugs: [{batch_no: "A1234567"}]}, + { id: 70 } + ] + } + + get '/validate_required_arrays_under_optional_arrays', data + expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!") + expect(last_response.status).to eq(200) + end + + it "simplest example using Arry -> Hash -> String" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Hash do requires :batch_no, type: String end end end + + subject.get '/validate_required_arrays_under_optional_arrays' do + 'validate_required_arrays_under_optional_arrays works!' + end + + data = { + orders: [ + { id: 77, drugs: {batch_no: "A1234567"}}, + { id: 70 } + ] + } + + get '/validate_required_arrays_under_optional_arrays', data + expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!") + expect(last_response.status).to eq(200) end - subject.get '/validate_required_arrays_under_optional_arrays' do - 'validate_required_arrays_under_optional_arrays works!' + it "correctly indexes invalida data" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Array do + requires :batch_no, type: String + requires :quantity, type: Integer + end + end + end + + subject.get '/correctly_indexes' do + 'correctly_indexes works!' + end + + data = { + orders: [ + { id: 70 }, + { id: 77, drugs: [{batch_no: "A1234567", quantity: 12}, {batch_no: "B222222"}]} + ] + } + + get '/correctly_indexes', data + expect(last_response.body).to eq("orders[1][drugs][1][quantity] is missing") + expect(last_response.status).to eq(400) end - data = { - orders: [ - { id: 77, drugs: [{batches: [{batch_no: "A1234567"}]}]}, - { id: 70 } - ] - } + context "multiple levels of optional and requires settings" do + before do + subject.params do + requires :top, type: Array do + requires :top_id, type: Integer, allow_blank: false + optional :middle_1, type: Array do + requires :middle_1_id, type: Integer, allow_blank: false + optional :middle_2, type: Array do + requires :middle_2_id, type: String, allow_blank: false + optional :bottom, type: Array do + requires :bottom_id, type: Integer, allow_blank: false + end + end + end + end + end - get '/validate_required_arrays_under_optional_arrays', data - expect(last_response.body).to eq("validate_required_arrays_under_optional_arrays works!") - expect(last_response.status).to eq(200) + subject.get '/multi_level' do + 'multi_level works!' + end + end + + it "with valid data" do + data_without_errors = { + top: [ + { top_id: 1, middle_1: [ + {middle_1_id: 11}, {middle_1_id: 12, middle_2: [ + {middle_2_id: 121}, {middle_2_id: 122, bottom: [{bottom_id: 1221}]}]}]}, + { top_id: 2, middle_1: [ + {middle_1_id: 21}, {middle_1_id: 22, middle_2: [ + {middle_2_id: 221}]}]}, + { top_id: 3, middle_1: [ + {middle_1_id: 31}, {middle_1_id: 32}]}, + { top_id: 4 } + ] + } + + get '/multi_level', data_without_errors + expect(last_response.body).to eq("multi_level works!") + expect(last_response.status).to eq(200) + end + + it "with invalid data" do + data = { + top: [ + { top_id: 1, middle_1: [ + {middle_1_id: 11}, {middle_1_id: 12, middle_2: [ + {middle_2_id: 121}, {middle_2_id: 122, bottom: [{bottom_id: nil}]}]}]}, + { top_id: 2, middle_1: [ + {middle_1_id: 21}, {middle_1_id: 22, middle_2: [{middle_2_id: nil}]}]}, + { top_id: 3, middle_1: [ + {middle_1_id: nil}, {middle_1_id: 32}]}, + { top_id: nil, missing_top_id: 4 } + ] + } + # debugger + get '/multi_level', data + expect(last_response.body.split(", ")).to match_array([ + "top[3][top_id] is empty", + "top[2][middle_1][0][middle_1_id] is empty", + "top[1][middle_1][1][middle_2][0][middle_2_id] is empty", + "top[0][middle_1][1][middle_2][1][bottom][0][bottom_id] is empty" + ]) + expect(last_response.status).to eq(400) + end + end end end From 413a935140d9218afda28a71c1699daf22960d49 Mon Sep 17 00:00:00 2001 From: David Henry Date: Tue, 10 Nov 2020 18:28:11 +0000 Subject: [PATCH 264/290] Fix incorrect optional validation issues for MultipleParamsBase Theese have previously been fixed for the Base class --- CHANGELOG.md | 1 + lib/grape/validations/attributes_iterator.rb | 8 ++ .../multiple_attributes_iterator.rb | 2 +- .../validations/single_attribute_iterator.rb | 9 -- .../validators/multiple_params_base.rb | 3 +- .../multiple_attributes_iterator_spec.rb | 16 +++- spec/grape/validations_spec.rb | 88 ++++++++++++++++++- 7 files changed, 111 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90ae03275..2ca271902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2129](https://github.com/ruby-grape/grape/pull/2129): Fix validation error when Required Array nested inside an optional array, for Multiparam validators - [@dwhenry](https://github.com/dwhenry). * [#2128](https://github.com/ruby-grape/grape/pull/2128): Fix validation error when Required Array nested inside an optional array - [@dwhenry](https://github.com/dwhenry). * [#2127](https://github.com/ruby-grape/grape/pull/2127): Fix a performance issue with dependent params - [@dnesteryuk](https://github.com/dnesteryuk). * [#2126](https://github.com/ruby-grape/grape/pull/2126): Fix warnings about redefined attribute accessors in `AttributeTranslator` - [@samsonjs](https://github.com/samsonjs). diff --git a/lib/grape/validations/attributes_iterator.rb b/lib/grape/validations/attributes_iterator.rb index 6c53d469a..c16d44f3f 100644 --- a/lib/grape/validations/attributes_iterator.rb +++ b/lib/grape/validations/attributes_iterator.rb @@ -48,6 +48,14 @@ def do_each(params_to_process, parent_indicies = [], &block) def yield_attributes(_resource_params, _attrs) raise NotImplementedError end + + # This is a special case so that we can ignore tree's where option + # values are missing lower down. Unfortunately we can remove this + # are the parameter parsing stage as they are required to ensure + # the correct indexing is maintained + def skip?(val) + val == Grape::DSL::Parameters::EmptyOptionalValue + end end end end diff --git a/lib/grape/validations/multiple_attributes_iterator.rb b/lib/grape/validations/multiple_attributes_iterator.rb index 49c7c2bc6..d9ef7264b 100644 --- a/lib/grape/validations/multiple_attributes_iterator.rb +++ b/lib/grape/validations/multiple_attributes_iterator.rb @@ -6,7 +6,7 @@ class MultipleAttributesIterator < AttributesIterator private def yield_attributes(resource_params, _attrs) - yield resource_params + yield resource_params, skip?(resource_params) end end end diff --git a/lib/grape/validations/single_attribute_iterator.rb b/lib/grape/validations/single_attribute_iterator.rb index 639b03957..7fd3c3f47 100644 --- a/lib/grape/validations/single_attribute_iterator.rb +++ b/lib/grape/validations/single_attribute_iterator.rb @@ -11,15 +11,6 @@ def yield_attributes(val, attrs) end end - - # This is a special case so that we can ignore tree's where option - # values are missing lower down. Unfortunately we can remove this - # are the parameter parsing stage as they are required to ensure - # the correct indexing is maintained - def skip?(val) - val == Grape::DSL::Parameters::EmptyOptionalValue - end - # Primitives like Integers and Booleans don't respond to +empty?+. # It could be possible to use +blank?+ instead, but # diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index 013386b59..03867ff1a 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -7,7 +7,8 @@ def validate!(params) attributes = MultipleAttributesIterator.new(self, @scope, params) array_errors = [] - attributes.each do |resource_params| + attributes.each do |resource_params, skip_value| + next if skip_value begin validate_params!(resource_params) rescue Grape::Exceptions::Validation => e diff --git a/spec/grape/validations/multiple_attributes_iterator_spec.rb b/spec/grape/validations/multiple_attributes_iterator_spec.rb index 85f848207..1508a76ed 100644 --- a/spec/grape/validations/multiple_attributes_iterator_spec.rb +++ b/spec/grape/validations/multiple_attributes_iterator_spec.rb @@ -13,8 +13,8 @@ { first: 'string', second: 'string' } end - it 'yields the whole params hash without the list of attrs' do - expect { |b| iterator.each(&b) }.to yield_with_args(params) + it 'yields the whole params hash and the skipped flag without the list of attrs' do + expect { |b| iterator.each(&b) }.to yield_with_args(params, false) end end @@ -24,7 +24,17 @@ end it 'yields each element of the array without the list of attrs' do - expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1]) + expect { |b| iterator.each(&b) }.to yield_successive_args([params[0], false], [params[1], false]) + end + end + + context 'when params is empty optional placeholder' do + let(:params) do + [Grape::DSL::Parameters::EmptyOptionalValue, { first: 'string2', second: 'string2' }] + end + + it 'yields each element of the array without the list of attrs' do + expect { |b| iterator.each(&b) }.to yield_successive_args([Grape::DSL::Parameters::EmptyOptionalValue, true], [params[1], false]) end end end diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 5f71a330e..7629b2bc1 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -1023,7 +1023,7 @@ def validate_param!(attr_name, params) end it "with valid data" do - data_without_errors = { + data = { top: [ { top_id: 1, middle_1: [ {middle_1_id: 11}, {middle_1_id: 12, middle_2: [ @@ -1037,7 +1037,7 @@ def validate_param!(attr_name, params) ] } - get '/multi_level', data_without_errors + get '/multi_level', data expect(last_response.body).to eq("multi_level works!") expect(last_response.status).to eq(200) end @@ -1067,6 +1067,90 @@ def validate_param!(attr_name, params) end end end + + it "exactly_one_of" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Hash do + optional :batch_no, type: String + optional :batch_id, type: String + exactly_one_of :batch_no, :batch_id + end + end + end + + subject.get '/exactly_one_of' do + 'exactly_one_of works!' + end + + data = { + orders: [ + { id: 77, drugs: {batch_no: "A1234567"}}, + { id: 70 } + ] + } + + get '/exactly_one_of', data + expect(last_response.body).to eq("exactly_one_of works!") + expect(last_response.status).to eq(200) + end + + it "at_least_one_of" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Hash do + optional :batch_no, type: String + optional :batch_id, type: String + at_least_one_of :batch_no, :batch_id + end + end + end + + subject.get '/at_least_one_of' do + 'at_least_one_of works!' + end + + data = { + orders: [ + { id: 77, drugs: {batch_no: "A1234567"}}, + { id: 70 } + ] + } + + get '/at_least_one_of', data + expect(last_response.body).to eq("at_least_one_of works!") + expect(last_response.status).to eq(200) + end + + it "all_or_none_of" do + subject.params do + requires :orders, type: Array do + requires :id, type: Integer + optional :drugs, type: Hash do + optional :batch_no, type: String + optional :batch_id, type: String + all_or_none_of :batch_no, :batch_id + end + end + end + + subject.get '/all_or_none_of' do + 'all_or_none_of works!' + end + + data = { + orders: [ + { id: 77, drugs: {batch_no: "A1234567", batch_id: "12"}}, + { id: 70 } + ] + } + + get '/all_or_none_of', data + expect(last_response.body).to eq("all_or_none_of works!") + expect(last_response.status).to eq(200) + end end context 'multiple validation errors' do From d00534851fac6ddd6b9b871149843a977b28dd3a Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 15 Nov 2020 15:27:42 +0200 Subject: [PATCH 265/290] Preparing for release, 1.5.1 --- CHANGELOG.md | 6 +----- README.md | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ca271902..ba17932d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,4 @@ -### 1.5.1 (Next) - -#### Features - -* Your contribution here. +### 1.5.1 (2020/11/15) #### Fixes diff --git a/README.md b/README.md index 3ad038cf4..5c17c3c76 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.5.1**. -Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.5.0](https://github.com/ruby-grape/grape/blob/v1.5.0/README.md). +You're reading the documentation for the stable release of Grape, 1.5.1. ## Project Resources From 88b6484a8ae692242133701dc6ce0e12fcb1886b Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 15 Nov 2020 15:30:15 +0200 Subject: [PATCH 266/290] Preparing for next development iteration, 1.5.2 --- CHANGELOG.md | 10 ++++++++++ README.md | 4 +++- lib/grape/version.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba17932d9..699c16b3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.5.2 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.5.1 (2020/11/15) #### Fixes diff --git a/README.md b/README.md index 5c17c3c76..0261cbc53 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, 1.5.1. +You're reading the documentation for the next release of Grape, which should be **1.5.2**. +Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.5.1](https://github.com/ruby-grape/grape/blob/v1.5.1/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index 5fd4319c1..b62f63d74 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.5.1' + VERSION = '1.5.2' end From 42489c243c78761238093c40f5af2b2bb9a05580 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sun, 15 Nov 2020 15:54:42 +0200 Subject: [PATCH 267/290] remove the placeholder from CHAGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 699c16b3f..ac5e2a5cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,6 @@ #### Fixes -* Your contribution here. * [#2129](https://github.com/ruby-grape/grape/pull/2129): Fix validation error when Required Array nested inside an optional array, for Multiparam validators - [@dwhenry](https://github.com/dwhenry). * [#2128](https://github.com/ruby-grape/grape/pull/2128): Fix validation error when Required Array nested inside an optional array - [@dwhenry](https://github.com/dwhenry). * [#2127](https://github.com/ruby-grape/grape/pull/2127): Fix a performance issue with dependent params - [@dnesteryuk](https://github.com/dnesteryuk). From 94c9d6ff88999e9f5476f25e32ff7c117db1317d Mon Sep 17 00:00:00 2001 From: K0H205 Date: Wed, 18 Nov 2020 09:43:13 +0900 Subject: [PATCH 268/290] Fix Ruby 2.7 keyword deprecation warning in validators/coerce (#2131) --- CHANGELOG.md | 1 + lib/grape/validations/validators/coerce.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5e2a5cc..1462d08d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). ### 1.5.1 (2020/11/15) diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 5b6f960a6..1b15c069d 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -17,7 +17,7 @@ class Instance module Validations class CoerceValidator < Base - def initialize(*_args) + def initialize(attrs, options, required, scope, **opts) super @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer) From a2f6725d5dbe4f76cc9ed252f0897b4b6cb4f8b3 Mon Sep 17 00:00:00 2001 From: Edward Rudd Date: Thu, 19 Nov 2020 16:16:55 -0500 Subject: [PATCH 269/290] fix PR reference in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1462d08d8..12d67e88d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,7 +29,7 @@ * [#2103](https://github.com/ruby-grape/grape/pull/2103): Ensure complete declared params structure is present - [@tlconnor](https://github.com/tlconnor). * [#2099](https://github.com/ruby-grape/grape/pull/2099): Added truffleruby to Travis-CI - [@gogainda](https://github.com/gogainda). * [#2089](https://github.com/ruby-grape/grape/pull/2089): Specify order of mounting Grape with Rack::Cascade in README - [@jonmchan](https://github.com/jonmchan). -* [#2083](https://github.com/ruby-grape/grape/pull/2083): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). +* [#2088](https://github.com/ruby-grape/grape/pull/2088): Set `Cache-Control` header only for streamed responses - [@stanhu](https://github.com/stanhu). * [#2092](https://github.com/ruby-grape/grape/pull/2092): Correct an example params in Include Missing doc - [@huyvohcmc](https://github.com/huyvohcmc). * [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim). * [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe). From 8cd284b6111f9b51ec5da1bde5f32e924dd9a074 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 19 Nov 2020 20:47:44 +0100 Subject: [PATCH 270/290] Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 * See https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/#a-compatible-delegation --- CHANGELOG.md | 1 + lib/grape/middleware/stack.rb | 30 ++++++++++++----------------- spec/grape/middleware/stack_spec.rb | 3 +-- 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1462d08d8..7377d3d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * Your contribution here. * [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). +* [#2132](https://github.com/ruby-grape/grape/pull/2132): Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 - [@eregon](https://github.com/eregon). ### 1.5.1 (2020/11/15) diff --git a/lib/grape/middleware/stack.rb b/lib/grape/middleware/stack.rb index a11869da8..dab755fe6 100644 --- a/lib/grape/middleware/stack.rb +++ b/lib/grape/middleware/stack.rb @@ -6,12 +6,11 @@ module Middleware # It allows to insert and insert after class Stack class Middleware - attr_reader :args, :opts, :block, :klass + attr_reader :args, :block, :klass - def initialize(klass, *args, **opts, &block) + def initialize(klass, *args, &block) @klass = klass - @args = args - @opts = opts + @args = args @block = block end @@ -32,16 +31,8 @@ def inspect klass.to_s end - if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7') - def use_in(builder) - block ? builder.use(klass, *args, **opts, &block) : builder.use(klass, *args, **opts) - end - else - def use_in(builder) - args = self.args - args += [opts] unless opts.empty? - block ? builder.use(klass, *args, &block) : builder.use(klass, *args) - end + def use_in(builder) + builder.use(@klass, *@args, &@block) end end @@ -70,11 +61,12 @@ def [](i) middlewares[i] end - def insert(index, *args, **kwargs, &block) + def insert(index, *args, &block) index = assert_index(index, :before) - middleware = self.class::Middleware.new(*args, **kwargs, &block) + middleware = self.class::Middleware.new(*args, &block) middlewares.insert(index, middleware) end + ruby2_keywords :insert if respond_to?(:ruby2_keywords, true) alias insert_before insert @@ -82,11 +74,13 @@ def insert_after(index, *args, &block) index = assert_index(index, :after) insert(index + 1, *args, &block) end + ruby2_keywords :insert_after if respond_to?(:ruby2_keywords, true) - def use(*args, **kwargs, &block) - middleware = self.class::Middleware.new(*args, **kwargs, &block) + def use(*args, &block) + middleware = self.class::Middleware.new(*args, &block) middlewares.push(middleware) end + ruby2_keywords :use if respond_to?(:ruby2_keywords, true) def merge_with(middleware_specs) middleware_specs.each do |operation, *args| diff --git a/spec/grape/middleware/stack_spec.rb b/spec/grape/middleware/stack_spec.rb index 64f9bf382..b7ac1b149 100644 --- a/spec/grape/middleware/stack_spec.rb +++ b/spec/grape/middleware/stack_spec.rb @@ -35,8 +35,7 @@ def initialize(&block) expect { subject.use StackSpec::BarMiddleware, false, my_arg: 42 } .to change { subject.size }.by(1) expect(subject.last).to eq(StackSpec::BarMiddleware) - expect(subject.last.args).to eq([false]) - expect(subject.last.opts).to eq(my_arg: 42) + expect(subject.last.args).to eq([false, { my_arg: 42 }]) end it 'pushes a middleware class with block arguments onto the stack' do From 6ce287027183b2a9edc9d2d5187b68d22dcdb321 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sun, 22 Nov 2020 21:50:04 +0200 Subject: [PATCH 271/290] Removed obsolete coercer benchmark --- benchmark/simple_with_type_coercer.rb | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 benchmark/simple_with_type_coercer.rb diff --git a/benchmark/simple_with_type_coercer.rb b/benchmark/simple_with_type_coercer.rb deleted file mode 100644 index 1dc0e7be4..000000000 --- a/benchmark/simple_with_type_coercer.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require 'grape' -require 'benchmark/ips' - -api = Class.new(Grape::API) do - prefix :api - version 'v1', using: :path - params do - requires :param, type: Array[String] - end - get '/' do - 'hello' - end -end - -env = Rack::MockRequest.env_for('/api/v1?param=value', method: 'GET') - -Benchmark.ips do |ips| - ips.report('simple_with_type_coercer') do - api.call(env) - end -end From 552af82c508ac16368bfd40c6962281312b95b25 Mon Sep 17 00:00:00 2001 From: Miyake J Takuma Date: Tue, 1 Dec 2020 16:55:20 +0900 Subject: [PATCH 272/290] Fix typos --- CHANGELOG.md | 1 + spec/grape/validations_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99aba0612..5cef5172f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2137](https://github.com/ruby-grape/grape/pull/2137): Fix typos - [@johnny-miyake](https://github.com/johnny-miyake). * [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). * [#2132](https://github.com/ruby-grape/grape/pull/2132): Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 - [@eregon](https://github.com/eregon). diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index 7629b2bc1..d325634f6 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -921,7 +921,7 @@ def validate_param!(attr_name, params) expect(last_response.status).to eq(200) end - it "simplest example using Arry -> Array -> Hash -> String" do + it "simplest example using Array -> Array -> Hash -> String" do subject.params do requires :orders, type: Array do requires :id, type: Integer @@ -947,7 +947,7 @@ def validate_param!(attr_name, params) expect(last_response.status).to eq(200) end - it "simplest example using Arry -> Hash -> String" do + it "simplest example using Array -> Hash -> String" do subject.params do requires :orders, type: Array do requires :id, type: Integer From e65bdc10c67a3775997ef04e718e22d781f95a11 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Mon, 28 Dec 2020 19:15:54 +0200 Subject: [PATCH 273/290] Explicitly require active_support/core_ext/array/conversions for the Array#to_xml method --- CHANGELOG.md | 1 + lib/grape.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cef5172f..c652b0844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ #### Fixes * Your contribution here. +* [#2144](https://github.com/ruby-grape/grape/pull/2144): Fix compatibility issue with activesupport 6.1 and XML serialization of arrays - [@anakinj](https://github.com/anakinj). * [#2137](https://github.com/ruby-grape/grape/pull/2137): Fix typos - [@johnny-miyake](https://github.com/johnny-miyake). * [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). * [#2132](https://github.com/ruby-grape/grape/pull/2132): Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 - [@eregon](https://github.com/eregon). diff --git a/lib/grape.rb b/lib/grape.rb index e2a0b9808..c1f313c2f 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -12,6 +12,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/except' From 9ede39ab0166c4d2aac7ce193246d07aa2085f85 Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Sat, 26 Dec 2020 22:45:59 +0200 Subject: [PATCH 274/290] Enable GitHub Actions with updated Rubocop and Danger --- .github/workflows/danger.yml | 19 +++ .github/workflows/test.yml | 80 +++++++++ .rubocop.yml | 1 + .rubocop_todo.yml | 305 ++++++++++++++++++++++++++++++++--- .travis.yml | 64 -------- CHANGELOG.md | 2 + Dangerfile | 2 +- Gemfile | 9 +- README.md | 2 +- 9 files changed, 387 insertions(+), 97 deletions(-) create mode 100644 .github/workflows/danger.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 000000000..681f52b46 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,19 @@ +--- +name: Danger +on: + pull_request: + ypes: [opened, reopened, edited, synchronize] +jobs: + danger: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v1 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 + bundler-cache: true + - name: Run Danger + run: bundle exec danger + env: + DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..8782c8247 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +--- +name: test +on: + push: + branches: + - "*" + pull_request: + branches: + - "*" +jobs: + lint: + name: RuboCop + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + - name: Run RuboCop + run: bundle exec rubocop + test: + strategy: + fail-fast: false + matrix: + ruby: + - 2.5 + - 2.6 + - 2.7 + gemfile: + - Gemfile + - gemfiles/rack1.gemfile + - gemfiles/rack2.gemfile + - gemfiles/rack_edge.gemfile + - gemfiles/rails_edge.gemfile + - gemfiles/rails_5.gemfile + - gemfiles/rails_6.gemfile + experimental: [false] + include: + - ruby: 2.7 + gemfile: 'gemfiles/multi_json.gemfile' + experimental: false + - ruby: 2.7 + gemfile: 'gemfiles/multi_xml.gemfile' + experimental: false + - ruby: "ruby-head" + experimental: true + - ruby: "truffleruby-head" + experimental: true + - ruby: "jruby-head" + experimental: true + runs-on: ubuntu-20.04 + continue-on-error: ${{ matrix.experimental }} + env: + BUNDLE_GEMFILE: ${{ matrix.gemfile }} + + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Run tests + run: bundle exec rake spec + + - name: Run tests (spec/integration/eager_load) + if: ${{ matrix.gemfile == 'Gemfile' }} + run: bundle exec rspec spec/integration/eager_load + + - name: Run tests (spec/integration/multi_json) + if: ${{ matrix.gemfile == 'gemfiles/multi_json.gemfile' }} + run: bundle exec rspec spec/integration/multi_json + + - name: Run tests (spec/integration/multi_xml) + if: ${{ matrix.gemfile == 'gemfiles/multi_xml.gemfile' }} + run: bundle exec rspec spec/integration/multi_xml diff --git a/.rubocop.yml b/.rubocop.yml index e03c5ca66..ee4a91c64 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,7 @@ AllCops: NewCops: enable TargetRubyVersion: 2.4 + SuggestExtensions: false Exclude: - vendor/**/* - bin/**/* diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 84b9fc45b..4320b3937 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-09-30 12:54:06 -0400 using RuboCop version 0.84.0. +# on 2020-12-26 22:10:33 UTC using RuboCop version 1.7.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 6 +# Offense count: 5 # Cop supports --auto-correct. Layout/ClosingHeredocIndentation: Exclude: @@ -18,6 +18,22 @@ Layout/ClosingHeredocIndentation: Layout/EmptyLineAfterGuardClause: Enabled: false +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. +Layout/EmptyLineBetweenDefs: + Exclude: + - 'spec/grape/api_spec.rb' + - 'spec/grape/middleware/stack_spec.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Layout/FirstArrayElementIndentation: + Exclude: + - 'spec/grape/validations_spec.rb' + # Offense count: 27 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. @@ -34,19 +50,90 @@ Layout/HashAlignment: # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: squiggly, active_support, powerpack, unindent Layout/HeredocIndentation: Exclude: - 'lib/grape/router/route.rb' - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineArrayBraceLayout: + Exclude: + - 'spec/grape/validations_spec.rb' + +# Offense count: 13 +# Cop supports --auto-correct. +Layout/SpaceBeforeBrackets: + Exclude: + - 'spec/grape/api_remount_spec.rb' + - 'spec/grape/dsl/desc_spec.rb' + - 'spec/grape/entity_spec.rb' + - 'spec/grape/exceptions/invalid_accept_header_spec.rb' + +# Offense count: 71 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideHashLiteralBraces: + Exclude: + - 'spec/grape/validations_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: + Exclude: + - 'spec/grape/validations_spec.rb' + # Offense count: 1 Lint/AmbiguousBlockAssociation: Exclude: - 'spec/grape/dsl/routing_spec.rb' +# Offense count: 54 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Enabled: false + +# Offense count: 5 +# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. +Lint/DuplicateBranch: + Exclude: + - 'lib/grape/extensions/deep_symbolize_hash.rb' + - 'spec/support/versioned_helpers.rb' + +# Offense count: 85 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Enabled: false + +# Offense count: 6 +# Configuration parameters: AllowComments. +Lint/EmptyClass: + Exclude: + - 'lib/grape/dsl/parameters.rb' + - 'lib/grape/validations/types.rb' + - 'spec/grape/api_spec.rb' + - 'spec/grape/entity_spec.rb' + - 'spec/grape/middleware/stack_spec.rb' + +# Offense count: 9 +Lint/MissingSuper: + Exclude: + - 'lib/grape/api.rb' + - 'lib/grape/api/instance.rb' + - 'lib/grape/exceptions/base.rb' + - 'lib/grape/exceptions/validation_array_errors.rb' + - 'lib/grape/namespace.rb' + - 'lib/grape/path.rb' + - 'lib/grape/router/pattern.rb' + - 'lib/grape/validations/validators/base.rb' + # Offense count: 1 # Cop supports --auto-correct. Lint/NonDeterministicRequireOrder: @@ -59,54 +146,68 @@ Lint/ToJSON: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 50 -# Configuration parameters: IgnoredMethods. +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowComments. +Lint/UselessMethodDefinition: + Exclude: + - 'lib/grape/validations/validators/coerce.rb' + +# Offense count: 42 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: - Max: 43 + Max: 47 # Offense count: 6 -# Configuration parameters: CountComments, ExcludedMethods. -# ExcludedMethods: refine +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine Metrics/BlockLength: Max: 182 # Offense count: 11 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 304 # Offense count: 30 # Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 14 + Max: 17 -# Offense count: 69 -# Configuration parameters: CountComments, ExcludedMethods. +# Offense count: 71 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 32 # Offense count: 12 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 220 -# Offense count: 25 +# Offense count: 1 +# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. +Metrics/ParameterLists: + Exclude: + - 'lib/grape/middleware/error.rb' + +# Offense count: 27 # Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: - Max: 14 + Max: 18 -# Offense count: 3 +# Offense count: 4 # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: Exclude: - 'lib/grape/api/instance.rb' + - 'lib/grape/config.rb' - 'lib/grape/middleware/base.rb' - 'spec/grape/integration/rack_spec.rb' # Offense count: 5 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp +# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: Exclude: - 'lib/grape/endpoint.rb' @@ -114,12 +215,95 @@ Naming/MethodParameterName: - 'lib/grape/middleware/stack.rb' - 'spec/grape/api_spec.rb' +# Offense count: 18 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +Naming/VariableNumber: + Exclude: + - 'spec/grape/dsl/settings_spec.rb' + - 'spec/grape/exceptions/validation_errors_spec.rb' + - 'spec/grape/validations_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Performance/BigDecimalWithNumericArgument: + Exclude: + - 'spec/grape/validations/types/primitive_coercer_spec.rb' + +# Offense count: 21 +# Cop supports --auto-correct. +Performance/BlockGivenWithExplicitBlock: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/dsl/desc.rb' + - 'lib/grape/dsl/helpers.rb' + - 'lib/grape/dsl/middleware.rb' + - 'lib/grape/dsl/parameters.rb' + - 'lib/grape/dsl/request_response.rb' + - 'lib/grape/dsl/routing.rb' + - 'lib/grape/dsl/settings.rb' + - 'lib/grape/endpoint.rb' + - 'lib/grape/validations/params_scope.rb' + +# Offense count: 2 +# Configuration parameters: MinSize. +Performance/CollectionLiteralInLoop: + Exclude: + - 'spec/grape/api_spec.rb' + - 'spec/grape/middleware/formatter_spec.rb' + # Offense count: 3 # Cop supports --auto-correct. Performance/InefficientHashSearch: Exclude: - 'spec/grape/validations/validators/values_spec.rb' +# Offense count: 1 +Performance/MethodObjectAsBlock: + Exclude: + - 'lib/grape/middleware/stack.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/exceptions/validation.rb' + - 'lib/grape/util/inheritable_setting.rb' + - 'spec/grape/middleware/error_spec.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/CaseLikeIf: + Exclude: + - 'lib/grape/util/lazy_value.rb' + - 'spec/grape/validations/validators/coerce_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: ==, equal?, eql? +Style/ClassEqualityComparison: + Exclude: + - 'lib/grape/validations/types/dry_type_coercer.rb' + - 'lib/grape/validations/validators/coerce.rb' + +# Offense count: 1 +Style/CombinableLoops: + Exclude: + - 'spec/grape/endpoint_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE +Style/CommentAnnotation: + Exclude: + - 'spec/grape/api_spec.rb' + # Offense count: 5 # Cop supports --auto-correct. Style/EmptyLambdaParameter: @@ -138,12 +322,24 @@ Style/ExpandPathArguments: - 'lib/grape.rb' - 'spec/grape/validations/validators/coerce_spec.rb' +# Offense count: 1 +# Cop supports --auto-correct. +Style/ExplicitBlockArgument: + Exclude: + - 'lib/grape/middleware/stack.rb' + # Offense count: 2 -# Configuration parameters: . +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed. # SupportedStyles: annotated, template, unannotated Style/FormatStringToken: EnforcedStyle: template +# Offense count: 1 +# Cop supports --auto-correct. +Style/GlobalStdStream: + Exclude: + - 'benchmark/remounting.rb' + # Offense count: 19 # Cop supports --auto-correct. Style/IfUnlessModifier: @@ -159,20 +355,41 @@ Style/IfUnlessModifier: - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/validations/params_scope.rb' -# Offense count: 1 -Style/MethodMissingSuper: - Exclude: - - 'lib/grape/router/attribute_translator.rb' - # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +# Configuration parameters: EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - 'spec/**/*' - 'lib/grape/middleware/formatter.rb' +# Offense count: 12 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/grape/api.rb' + - 'lib/grape/dsl/inside_route.rb' + - 'lib/grape/dsl/parameters.rb' + - 'lib/grape/endpoint.rb' + - 'lib/grape/serve_stream/sendfile_response.rb' + - 'lib/grape/validations/params_scope.rb' + - 'lib/grape/validations/types/array_coercer.rb' + - 'lib/grape/validations/types/custom_type_collection_coercer.rb' + - 'lib/grape/validations/types/dry_type_coercer.rb' + - 'lib/grape/validations/types/primitive_coercer.rb' + - 'lib/grape/validations/types/set_coercer.rb' + +# Offense count: 18 +# Cop supports --auto-correct. +Style/RedundantRegexpEscape: + Exclude: + - 'lib/grape/middleware/versioner/header.rb' + - 'lib/grape/middleware/versioner/parse_media_type_patch.rb' + - 'spec/grape/api/routes_with_requirements_spec.rb' + - 'spec/grape/api_spec.rb' + # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. @@ -187,6 +404,42 @@ Style/SafeNavigation: - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/middleware/versioner/header.rb' +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: AllowModifier. +Style/SoleNestedConditional: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/middleware/versioner/accept_version_header.rb' + - 'lib/grape/validations/params_scope.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +Style/StringConcatenation: + Exclude: + - 'benchmark/large_model.rb' + - 'lib/grape/dsl/inside_route.rb' + - 'lib/grape/router/pattern.rb' + - 'spec/grape/validations/validators/values_spec.rb' + - 'spec/shared/versioning_examples.rb' + - 'spec/support/basic_auth_encode_helpers.rb' + +# Offense count: 32 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'spec/grape/validations_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArguments: + Exclude: + - 'spec/grape/validations/single_attribute_iterator_spec.rb' + # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinSize, WordRegex. @@ -196,7 +449,7 @@ Style/WordArray: - 'spec/grape/validations/validators/except_values_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 131 +# Offense count: 132 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 48632523d..000000000 --- a/.travis.yml +++ /dev/null @@ -1,64 +0,0 @@ -language: ruby - -sudo: false - -# "gemfile" is required for "allow_failures" option, -# see https://docs.travis-ci.com/user/customizing-the-build/#matching-jobs-with-allow_failures -gemfile: - -script: bundle exec rake spec - -matrix: - include: - - rvm: 2.7.1 - script: - - bundle exec danger - - rvm: 2.7.1 - gemfile: Gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rack1.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rack2.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rails_6.gemfile - - rvm: 2.7.1 - gemfile: Gemfile - script: - - bundle exec rspec spec/integration/eager_load - - rvm: 2.7.1 - gemfile: gemfiles/multi_json.gemfile - script: - - bundle exec rspec spec/integration/multi_json - - rvm: 2.7.1 - gemfile: gemfiles/multi_xml.gemfile - script: - - bundle exec rspec spec/integration/multi_xml - - rvm: 2.6.6 - gemfile: Gemfile - - rvm: 2.6.6 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.6.6 - gemfile: gemfiles/rails_6.gemfile - - rvm: 2.5.8 - gemfile: Gemfile - - rvm: 2.5.8 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.5.8 - gemfile: gemfiles/rails_6.gemfile - - rvm: ruby-head - - rvm: jruby-head - - rvm: rbx-3 - - rvm: truffleruby-head - allow_failures: - - rvm: ruby-head - - rvm: jruby-head - - rvm: rbx-3 - - rvm: truffleruby-head - -bundler_args: --without development diff --git a/CHANGELOG.md b/CHANGELOG.md index c652b0844..9197d989d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ #### Features * Your contribution here. +* [#2143](https://github.com/ruby-grape/grape/pull/2143): Enable GitHub Actions with updated RuboCop and Danger - [@anakinj](https://github.com/anakinj). + #### Fixes diff --git a/Dangerfile b/Dangerfile index 2f1200640..527dbb8b7 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,4 +1,4 @@ # frozen_string_literal: true danger.import_dangerfile(gem: 'ruby-grape-danger') -toc.check +toc.check! diff --git a/Gemfile b/Gemfile index 99de12cfb..6c4124a52 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,9 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do @@ -27,12 +27,11 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end diff --git a/README.md b/README.md index 0261cbc53..9e3a1dd6c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![grape logo](grape.png) [![Gem Version](https://badge.fury.io/rb/grape.svg)](http://badge.fury.io/rb/grape) -[![Build Status](https://travis-ci.org/ruby-grape/grape.svg?branch=master)](https://travis-ci.org/ruby-grape/grape) +[![Build Status](https://github.com/ruby-grape/grape/workflows/test/badge.svg?branch=master)](https://github.com/ruby-grape/grape/actions) [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape) [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master) [![Inline docs](https://inch-ci.org/github/ruby-grape/grape.svg)](https://inch-ci.org/github/ruby-grape/grape) From 7f4f72c5e3750ab3b903b24ea940882bdbb1f111 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Mon, 28 Dec 2020 18:46:37 +0100 Subject: [PATCH 275/290] Add ruby 3.0.0 travis Add rails_6_1.gemfile Add rails_6_1.gemfile Transform **options into *options since Rack::Builder is calling middleware with .new(klass, *args, &block) Add ** to versioned_path Explicit ** for macro options Replace ** by * since its anonymous Add explicit { } Transform **opts to *opts since its called with with (*args, &block) Add explicit ** Transform **opts to *opts since rspec-mock is calling .new(*args) Add 6_1 gemfile Add CHANGELOG Explicitly require active_support/core_ext/array/conversions for the Array#to_xml method Enable GitHub Actions with updated Rubocop and Danger Merge master Rubocop gemfiles Explicitly require active_support/core_ext/array/conversions for the Array#to_xml method --- .github/workflows/danger.yml | 19 ++ .github/workflows/test.yml | 80 ++++++ .rubocop.yml | 1 + .rubocop_todo.yml | 305 ++++++++++++++++++++-- .travis.yml | 64 ----- Appraisals | 4 + CHANGELOG.md | 3 + Dangerfile | 2 +- Gemfile | 9 +- README.md | 2 +- gemfiles/multi_json.gemfile | 10 +- gemfiles/multi_xml.gemfile | 10 +- gemfiles/rack1.gemfile | 10 +- gemfiles/rack2.gemfile | 10 +- gemfiles/rack_edge.gemfile | 10 +- gemfiles/rails_5.gemfile | 10 +- gemfiles/rails_6.gemfile | 10 +- gemfiles/rails_6_1.gemfile | 39 +++ gemfiles/rails_edge.gemfile | 10 +- lib/grape.rb | 1 + lib/grape/dsl/routing.rb | 5 +- lib/grape/exceptions/validation_errors.rb | 2 +- lib/grape/middleware/auth/base.rb | 6 +- lib/grape/middleware/base.rb | 4 +- lib/grape/middleware/error.rb | 2 +- lib/grape/validations/validators/base.rb | 10 +- spec/grape/request_spec.rb | 2 +- spec/shared/versioning_examples.rb | 40 +-- spec/support/versioned_helpers.rb | 2 +- 29 files changed, 510 insertions(+), 172 deletions(-) create mode 100644 .github/workflows/danger.yml create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml create mode 100644 gemfiles/rails_6_1.gemfile diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml new file mode 100644 index 000000000..681f52b46 --- /dev/null +++ b/.github/workflows/danger.yml @@ -0,0 +1,19 @@ +--- +name: Danger +on: + pull_request: + ypes: [opened, reopened, edited, synchronize] +jobs: + danger: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v1 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6 + bundler-cache: true + - name: Run Danger + run: bundle exec danger + env: + DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..8782c8247 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,80 @@ +--- +name: test +on: + push: + branches: + - "*" + pull_request: + branches: + - "*" +jobs: + lint: + name: RuboCop + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + - name: Run RuboCop + run: bundle exec rubocop + test: + strategy: + fail-fast: false + matrix: + ruby: + - 2.5 + - 2.6 + - 2.7 + gemfile: + - Gemfile + - gemfiles/rack1.gemfile + - gemfiles/rack2.gemfile + - gemfiles/rack_edge.gemfile + - gemfiles/rails_edge.gemfile + - gemfiles/rails_5.gemfile + - gemfiles/rails_6.gemfile + experimental: [false] + include: + - ruby: 2.7 + gemfile: 'gemfiles/multi_json.gemfile' + experimental: false + - ruby: 2.7 + gemfile: 'gemfiles/multi_xml.gemfile' + experimental: false + - ruby: "ruby-head" + experimental: true + - ruby: "truffleruby-head" + experimental: true + - ruby: "jruby-head" + experimental: true + runs-on: ubuntu-20.04 + continue-on-error: ${{ matrix.experimental }} + env: + BUNDLE_GEMFILE: ${{ matrix.gemfile }} + + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Run tests + run: bundle exec rake spec + + - name: Run tests (spec/integration/eager_load) + if: ${{ matrix.gemfile == 'Gemfile' }} + run: bundle exec rspec spec/integration/eager_load + + - name: Run tests (spec/integration/multi_json) + if: ${{ matrix.gemfile == 'gemfiles/multi_json.gemfile' }} + run: bundle exec rspec spec/integration/multi_json + + - name: Run tests (spec/integration/multi_xml) + if: ${{ matrix.gemfile == 'gemfiles/multi_xml.gemfile' }} + run: bundle exec rspec spec/integration/multi_xml diff --git a/.rubocop.yml b/.rubocop.yml index e03c5ca66..ee4a91c64 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,7 @@ AllCops: NewCops: enable TargetRubyVersion: 2.4 + SuggestExtensions: false Exclude: - vendor/**/* - bin/**/* diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 84b9fc45b..4320b3937 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,12 +1,12 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2020-09-30 12:54:06 -0400 using RuboCop version 0.84.0. +# on 2020-12-26 22:10:33 UTC using RuboCop version 1.7.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 6 +# Offense count: 5 # Cop supports --auto-correct. Layout/ClosingHeredocIndentation: Exclude: @@ -18,6 +18,22 @@ Layout/ClosingHeredocIndentation: Layout/EmptyLineAfterGuardClause: Enabled: false +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. +Layout/EmptyLineBetweenDefs: + Exclude: + - 'spec/grape/api_spec.rb' + - 'spec/grape/middleware/stack_spec.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Layout/FirstArrayElementIndentation: + Exclude: + - 'spec/grape/validations_spec.rb' + # Offense count: 27 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. @@ -34,19 +50,90 @@ Layout/HashAlignment: # Offense count: 7 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: squiggly, active_support, powerpack, unindent Layout/HeredocIndentation: Exclude: - 'lib/grape/router/route.rb' - 'spec/grape/api_spec.rb' - 'spec/grape/entity_spec.rb' +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: symmetrical, new_line, same_line +Layout/MultilineArrayBraceLayout: + Exclude: + - 'spec/grape/validations_spec.rb' + +# Offense count: 13 +# Cop supports --auto-correct. +Layout/SpaceBeforeBrackets: + Exclude: + - 'spec/grape/api_remount_spec.rb' + - 'spec/grape/dsl/desc_spec.rb' + - 'spec/grape/entity_spec.rb' + - 'spec/grape/exceptions/invalid_accept_header_spec.rb' + +# Offense count: 71 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. +# SupportedStyles: space, no_space, compact +# SupportedStylesForEmptyBraces: space, no_space +Layout/SpaceInsideHashLiteralBraces: + Exclude: + - 'spec/grape/validations_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowInHeredoc. +Layout/TrailingWhitespace: + Exclude: + - 'spec/grape/validations_spec.rb' + # Offense count: 1 Lint/AmbiguousBlockAssociation: Exclude: - 'spec/grape/dsl/routing_spec.rb' +# Offense count: 54 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Enabled: false + +# Offense count: 5 +# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. +Lint/DuplicateBranch: + Exclude: + - 'lib/grape/extensions/deep_symbolize_hash.rb' + - 'spec/support/versioned_helpers.rb' + +# Offense count: 85 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Enabled: false + +# Offense count: 6 +# Configuration parameters: AllowComments. +Lint/EmptyClass: + Exclude: + - 'lib/grape/dsl/parameters.rb' + - 'lib/grape/validations/types.rb' + - 'spec/grape/api_spec.rb' + - 'spec/grape/entity_spec.rb' + - 'spec/grape/middleware/stack_spec.rb' + +# Offense count: 9 +Lint/MissingSuper: + Exclude: + - 'lib/grape/api.rb' + - 'lib/grape/api/instance.rb' + - 'lib/grape/exceptions/base.rb' + - 'lib/grape/exceptions/validation_array_errors.rb' + - 'lib/grape/namespace.rb' + - 'lib/grape/path.rb' + - 'lib/grape/router/pattern.rb' + - 'lib/grape/validations/validators/base.rb' + # Offense count: 1 # Cop supports --auto-correct. Lint/NonDeterministicRequireOrder: @@ -59,54 +146,68 @@ Lint/ToJSON: Exclude: - 'spec/grape/middleware/formatter_spec.rb' -# Offense count: 50 -# Configuration parameters: IgnoredMethods. +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AllowComments. +Lint/UselessMethodDefinition: + Exclude: + - 'lib/grape/validations/validators/coerce.rb' + +# Offense count: 42 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. Metrics/AbcSize: - Max: 43 + Max: 47 # Offense count: 6 -# Configuration parameters: CountComments, ExcludedMethods. -# ExcludedMethods: refine +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. +# IgnoredMethods: refine Metrics/BlockLength: Max: 182 # Offense count: 11 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ClassLength: Max: 304 # Offense count: 30 # Configuration parameters: IgnoredMethods. Metrics/CyclomaticComplexity: - Max: 14 + Max: 17 -# Offense count: 69 -# Configuration parameters: CountComments, ExcludedMethods. +# Offense count: 71 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods. Metrics/MethodLength: Max: 32 # Offense count: 12 -# Configuration parameters: CountComments. +# Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: Max: 220 -# Offense count: 25 +# Offense count: 1 +# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. +Metrics/ParameterLists: + Exclude: + - 'lib/grape/middleware/error.rb' + +# Offense count: 27 # Configuration parameters: IgnoredMethods. Metrics/PerceivedComplexity: - Max: 14 + Max: 18 -# Offense count: 3 +# Offense count: 4 # Configuration parameters: EnforcedStyleForLeadingUnderscores. # SupportedStylesForLeadingUnderscores: disallowed, required, optional Naming/MemoizedInstanceVariableName: Exclude: - 'lib/grape/api/instance.rb' + - 'lib/grape/config.rb' - 'lib/grape/middleware/base.rb' - 'spec/grape/integration/rack_spec.rb' # Offense count: 5 # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. -# AllowedNames: io, id, to, by, on, in, at, ip, db, os, pp +# AllowedNames: at, by, db, id, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: Exclude: - 'lib/grape/endpoint.rb' @@ -114,12 +215,95 @@ Naming/MethodParameterName: - 'lib/grape/middleware/stack.rb' - 'spec/grape/api_spec.rb' +# Offense count: 18 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +Naming/VariableNumber: + Exclude: + - 'spec/grape/dsl/settings_spec.rb' + - 'spec/grape/exceptions/validation_errors_spec.rb' + - 'spec/grape/validations_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Performance/BigDecimalWithNumericArgument: + Exclude: + - 'spec/grape/validations/types/primitive_coercer_spec.rb' + +# Offense count: 21 +# Cop supports --auto-correct. +Performance/BlockGivenWithExplicitBlock: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/dsl/desc.rb' + - 'lib/grape/dsl/helpers.rb' + - 'lib/grape/dsl/middleware.rb' + - 'lib/grape/dsl/parameters.rb' + - 'lib/grape/dsl/request_response.rb' + - 'lib/grape/dsl/routing.rb' + - 'lib/grape/dsl/settings.rb' + - 'lib/grape/endpoint.rb' + - 'lib/grape/validations/params_scope.rb' + +# Offense count: 2 +# Configuration parameters: MinSize. +Performance/CollectionLiteralInLoop: + Exclude: + - 'spec/grape/api_spec.rb' + - 'spec/grape/middleware/formatter_spec.rb' + # Offense count: 3 # Cop supports --auto-correct. Performance/InefficientHashSearch: Exclude: - 'spec/grape/validations/validators/values_spec.rb' +# Offense count: 1 +Performance/MethodObjectAsBlock: + Exclude: + - 'lib/grape/middleware/stack.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle. +# SupportedStyles: separated, grouped +Style/AccessorGrouping: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/exceptions/validation.rb' + - 'lib/grape/util/inheritable_setting.rb' + - 'spec/grape/middleware/error_spec.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Style/CaseLikeIf: + Exclude: + - 'lib/grape/util/lazy_value.rb' + - 'spec/grape/validations/validators/coerce_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: ==, equal?, eql? +Style/ClassEqualityComparison: + Exclude: + - 'lib/grape/validations/types/dry_type_coercer.rb' + - 'lib/grape/validations/validators/coerce.rb' + +# Offense count: 1 +Style/CombinableLoops: + Exclude: + - 'spec/grape/endpoint_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW, NOTE +Style/CommentAnnotation: + Exclude: + - 'spec/grape/api_spec.rb' + # Offense count: 5 # Cop supports --auto-correct. Style/EmptyLambdaParameter: @@ -138,12 +322,24 @@ Style/ExpandPathArguments: - 'lib/grape.rb' - 'spec/grape/validations/validators/coerce_spec.rb' +# Offense count: 1 +# Cop supports --auto-correct. +Style/ExplicitBlockArgument: + Exclude: + - 'lib/grape/middleware/stack.rb' + # Offense count: 2 -# Configuration parameters: . +# Configuration parameters: MaxUnannotatedPlaceholdersAllowed. # SupportedStyles: annotated, template, unannotated Style/FormatStringToken: EnforcedStyle: template +# Offense count: 1 +# Cop supports --auto-correct. +Style/GlobalStdStream: + Exclude: + - 'benchmark/remounting.rb' + # Offense count: 19 # Cop supports --auto-correct. Style/IfUnlessModifier: @@ -159,20 +355,41 @@ Style/IfUnlessModifier: - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/validations/params_scope.rb' -# Offense count: 1 -Style/MethodMissingSuper: - Exclude: - - 'lib/grape/router/attribute_translator.rb' - # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods. +# Configuration parameters: EnforcedStyle, IgnoredMethods. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - 'spec/**/*' - 'lib/grape/middleware/formatter.rb' +# Offense count: 12 +# Configuration parameters: AllowedMethods. +# AllowedMethods: respond_to_missing? +Style/OptionalBooleanParameter: + Exclude: + - 'lib/grape/api.rb' + - 'lib/grape/dsl/inside_route.rb' + - 'lib/grape/dsl/parameters.rb' + - 'lib/grape/endpoint.rb' + - 'lib/grape/serve_stream/sendfile_response.rb' + - 'lib/grape/validations/params_scope.rb' + - 'lib/grape/validations/types/array_coercer.rb' + - 'lib/grape/validations/types/custom_type_collection_coercer.rb' + - 'lib/grape/validations/types/dry_type_coercer.rb' + - 'lib/grape/validations/types/primitive_coercer.rb' + - 'lib/grape/validations/types/set_coercer.rb' + +# Offense count: 18 +# Cop supports --auto-correct. +Style/RedundantRegexpEscape: + Exclude: + - 'lib/grape/middleware/versioner/header.rb' + - 'lib/grape/middleware/versioner/parse_media_type_patch.rb' + - 'spec/grape/api/routes_with_requirements_spec.rb' + - 'spec/grape/api_spec.rb' + # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. @@ -187,6 +404,42 @@ Style/SafeNavigation: - 'lib/grape/middleware/versioner/accept_version_header.rb' - 'lib/grape/middleware/versioner/header.rb' +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: AllowModifier. +Style/SoleNestedConditional: + Exclude: + - 'lib/grape/api/instance.rb' + - 'lib/grape/middleware/versioner/accept_version_header.rb' + - 'lib/grape/validations/params_scope.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +Style/StringConcatenation: + Exclude: + - 'benchmark/large_model.rb' + - 'lib/grape/dsl/inside_route.rb' + - 'lib/grape/router/pattern.rb' + - 'spec/grape/validations/validators/values_spec.rb' + - 'spec/shared/versioning_examples.rb' + - 'spec/support/basic_auth_encode_helpers.rb' + +# Offense count: 32 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiterals: + Exclude: + - 'spec/grape/validations_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyleForMultiline. +# SupportedStylesForMultiline: comma, consistent_comma, no_comma +Style/TrailingCommaInArguments: + Exclude: + - 'spec/grape/validations/single_attribute_iterator_spec.rb' + # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinSize, WordRegex. @@ -196,7 +449,7 @@ Style/WordArray: - 'spec/grape/validations/validators/except_values_spec.rb' - 'spec/grape/validations/validators/values_spec.rb' -# Offense count: 131 +# Offense count: 132 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 48632523d..000000000 --- a/.travis.yml +++ /dev/null @@ -1,64 +0,0 @@ -language: ruby - -sudo: false - -# "gemfile" is required for "allow_failures" option, -# see https://docs.travis-ci.com/user/customizing-the-build/#matching-jobs-with-allow_failures -gemfile: - -script: bundle exec rake spec - -matrix: - include: - - rvm: 2.7.1 - script: - - bundle exec danger - - rvm: 2.7.1 - gemfile: Gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rack1.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rack2.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rack_edge.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rails_edge.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.7.1 - gemfile: gemfiles/rails_6.gemfile - - rvm: 2.7.1 - gemfile: Gemfile - script: - - bundle exec rspec spec/integration/eager_load - - rvm: 2.7.1 - gemfile: gemfiles/multi_json.gemfile - script: - - bundle exec rspec spec/integration/multi_json - - rvm: 2.7.1 - gemfile: gemfiles/multi_xml.gemfile - script: - - bundle exec rspec spec/integration/multi_xml - - rvm: 2.6.6 - gemfile: Gemfile - - rvm: 2.6.6 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.6.6 - gemfile: gemfiles/rails_6.gemfile - - rvm: 2.5.8 - gemfile: Gemfile - - rvm: 2.5.8 - gemfile: gemfiles/rails_5.gemfile - - rvm: 2.5.8 - gemfile: gemfiles/rails_6.gemfile - - rvm: ruby-head - - rvm: jruby-head - - rvm: rbx-3 - - rvm: truffleruby-head - allow_failures: - - rvm: ruby-head - - rvm: jruby-head - - rvm: rbx-3 - - rvm: truffleruby-head - -bundler_args: --without development diff --git a/Appraisals b/Appraisals index 7e17deba5..1613cfa03 100644 --- a/Appraisals +++ b/Appraisals @@ -8,6 +8,10 @@ appraise 'rails-6' do gem 'rails', '~> 6.0' end +appraise 'rails-6-1' do + gem 'rails', '~> 6.1' +end + appraise 'rails-edge' do gem 'rails', github: 'rails/rails' end diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cef5172f..7d89d9352 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,13 @@ #### Features * Your contribution here. +* [#2143](https://github.com/ruby-grape/grape/pull/2143): Enable GitHub Actions with updated RuboCop and Danger - [@anakinj](https://github.com/anakinj). +* [#2145](https://github.com/ruby-grape/grape/pull/2145): Ruby 3.0 compatibility - [@ericproulx](https://github.com/ericproulx). #### Fixes * Your contribution here. +* [#2144](https://github.com/ruby-grape/grape/pull/2144): Fix compatibility issue with activesupport 6.1 and XML serialization of arrays - [@anakinj](https://github.com/anakinj). * [#2137](https://github.com/ruby-grape/grape/pull/2137): Fix typos - [@johnny-miyake](https://github.com/johnny-miyake). * [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). * [#2132](https://github.com/ruby-grape/grape/pull/2132): Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 - [@eregon](https://github.com/eregon). diff --git a/Dangerfile b/Dangerfile index 2f1200640..527dbb8b7 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,4 +1,4 @@ # frozen_string_literal: true danger.import_dangerfile(gem: 'ruby-grape-danger') -toc.check +toc.check! diff --git a/Gemfile b/Gemfile index 99de12cfb..6c4124a52 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,9 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do @@ -27,12 +27,11 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end diff --git a/README.md b/README.md index 0261cbc53..9e3a1dd6c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![grape logo](grape.png) [![Gem Version](https://badge.fury.io/rb/grape.svg)](http://badge.fury.io/rb/grape) -[![Build Status](https://travis-ci.org/ruby-grape/grape.svg?branch=master)](https://travis-ci.org/ruby-grape/grape) +[![Build Status](https://github.com/ruby-grape/grape/workflows/test/badge.svg?branch=master)](https://github.com/ruby-grape/grape/actions) [![Code Climate](https://codeclimate.com/github/ruby-grape/grape.svg)](https://codeclimate.com/github/ruby-grape/grape) [![Coverage Status](https://coveralls.io/repos/github/ruby-grape/grape/badge.svg?branch=master)](https://coveralls.io/github/ruby-grape/grape?branch=master) [![Inline docs](https://inch-ci.org/github/ruby-grape/grape.svg)](https://inch-ci.org/github/ruby-grape/grape) diff --git a/gemfiles/multi_json.gemfile b/gemfiles/multi_json.gemfile index 279eabba5..726c8be19 100644 --- a/gemfiles/multi_json.gemfile +++ b/gemfiles/multi_json.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/multi_xml.gemfile b/gemfiles/multi_xml.gemfile index ba20f9415..72d4e3f30 100644 --- a/gemfiles/multi_xml.gemfile +++ b/gemfiles/multi_xml.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/rack1.gemfile b/gemfiles/rack1.gemfile index 05e84c399..bb72144a0 100644 --- a/gemfiles/rack1.gemfile +++ b/gemfiles/rack1.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/rack2.gemfile b/gemfiles/rack2.gemfile index accc673b3..6522140d9 100644 --- a/gemfiles/rack2.gemfile +++ b/gemfiles/rack2.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/rack_edge.gemfile b/gemfiles/rack_edge.gemfile index d4271ef76..25b275b6c 100644 --- a/gemfiles/rack_edge.gemfile +++ b/gemfiles/rack_edge.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/rails_5.gemfile b/gemfiles/rails_5.gemfile index b32c7d3ac..7dc94e695 100644 --- a/gemfiles/rails_5.gemfile +++ b/gemfiles/rails_5.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile index 85e5f69fc..5db9b049d 100644 --- a/gemfiles/rails_6.gemfile +++ b/gemfiles/rails_6.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_6_1.gemfile new file mode 100644 index 000000000..d27f9ed02 --- /dev/null +++ b/gemfiles/rails_6_1.gemfile @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source 'https://rubygems.org' + +gem 'rails', '~> 6.1' + +group :development, :test do + gem 'bundler' + gem 'hashie' + gem 'rake' + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false +end + +group :development do + gem 'appraisal' + gem 'benchmark-ips' + gem 'benchmark-memory' + gem 'guard' + gem 'guard-rspec' + gem 'guard-rubocop' +end + +group :test do + gem 'cookiejar' + gem 'coveralls_reborn' + gem 'grape-entity', '~> 0.6' + gem 'maruku' + gem 'mime-types' + gem 'rack-jsonp', require: 'rack/jsonp' + gem 'rack-test', '~> 1.1.0' + gem 'rspec', '~> 3.0' + gem 'ruby-grape-danger', '~> 0.2.0', require: false +end + +gemspec path: '../' diff --git a/gemfiles/rails_edge.gemfile b/gemfiles/rails_edge.gemfile index bfb0d3f72..5d179f078 100644 --- a/gemfiles/rails_edge.gemfile +++ b/gemfiles/rails_edge.gemfile @@ -10,14 +10,15 @@ group :development, :test do gem 'bundler' gem 'hashie' gem 'rake' - gem 'rubocop', '0.84.0' - gem 'rubocop-ast', '< 0.7' - gem 'rubocop-performance', require: false + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false end group :development do gem 'appraisal' gem 'benchmark-ips' + gem 'benchmark-memory' gem 'guard' gem 'guard-rspec' gem 'guard-rubocop' @@ -26,14 +27,13 @@ end group :test do gem 'cookiejar' gem 'coveralls_reborn' - gem 'danger-toc', '~> 0.1.2' gem 'grape-entity', '~> 0.6' gem 'maruku' gem 'mime-types' gem 'rack-jsonp', require: 'rack/jsonp' gem 'rack-test', '~> 1.1.0' gem 'rspec', '~> 3.0' - gem 'ruby-grape-danger', '~> 0.1.0', require: false + gem 'ruby-grape-danger', '~> 0.2.0', require: false end gemspec path: '../' diff --git a/lib/grape.rb b/lib/grape.rb index e2a0b9808..c1f313c2f 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -12,6 +12,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/hash/except' diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index 76a22a79f..61bf1f21a 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -79,11 +79,12 @@ def do_not_route_options! namespace_inheritable(:do_not_route_options, true) end - def mount(mounts, **opts) + def mount(mounts, *opts) mounts = { mounts => '/' } unless mounts.respond_to?(:each_pair) mounts.each_pair do |app, path| if app.respond_to?(:mount_instance) - mount(app.mount_instance(configuration: opts[:with] || {}) => path) + opts_with = opts.any? ? opts.shift[:with] : {} + mount({ app.mount_instance(configuration: opts_with) => path }) next end in_setting = inheritable_setting diff --git a/lib/grape/exceptions/validation_errors.rb b/lib/grape/exceptions/validation_errors.rb index f8bf32e31..20f7dd2bf 100644 --- a/lib/grape/exceptions/validation_errors.rb +++ b/lib/grape/exceptions/validation_errors.rb @@ -39,7 +39,7 @@ def as_json(**_opts) end end - def to_json(**_opts) + def to_json(*_opts) as_json.to_json end diff --git a/lib/grape/middleware/auth/base.rb b/lib/grape/middleware/auth/base.rb index 61a8cc588..081613c92 100644 --- a/lib/grape/middleware/auth/base.rb +++ b/lib/grape/middleware/auth/base.rb @@ -10,9 +10,9 @@ class Base attr_accessor :options, :app, :env - def initialize(app, **options) + def initialize(app, *options) @app = app - @options = options + @options = options.shift end def call(env) @@ -23,7 +23,7 @@ def _call(env) self.env = env if options.key?(:type) - auth_proc = options[:proc] + auth_proc = options[:proc] auth_proc_context = context strategy_info = Grape::Middleware::Auth::Strategies[options[:type]] diff --git a/lib/grape/middleware/base.rb b/lib/grape/middleware/base.rb index 0e0f1729c..730a6cb3c 100644 --- a/lib/grape/middleware/base.rb +++ b/lib/grape/middleware/base.rb @@ -15,9 +15,9 @@ class Base # @param [Rack Application] app The standard argument for a Rack middleware. # @param [Hash] options A hash of options, simply stored for use by subclasses. - def initialize(app, **options) + def initialize(app, *options) @app = app - @options = default_options.merge(options) + @options = options.any? ? default_options.merge(options.shift) : default_options @app_response = nil end diff --git a/lib/grape/middleware/error.rb b/lib/grape/middleware/error.rb index 54bded3c8..20f6a89f3 100644 --- a/lib/grape/middleware/error.rb +++ b/lib/grape/middleware/error.rb @@ -27,7 +27,7 @@ def default_options } end - def initialize(app, **options) + def initialize(app, *options) super self.class.send(:include, @options[:helpers]) if @options[:helpers] end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index af7030f14..f45a254ca 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -12,14 +12,16 @@ class Base # @param options [Object] implementation-dependent Validator options # @param required [Boolean] attribute(s) are required or optional # @param scope [ParamsScope] parent scope for this Validator - # @param opts [Hash] additional validation options - def initialize(attrs, options, required, scope, **opts) + # @param opts [Array] additional validation options + def initialize(attrs, options, required, scope, *opts) @attrs = Array(attrs) @option = options @required = required @scope = scope - @fail_fast = opts[:fail_fast] || false - @allow_blank = opts[:allow_blank] || false + @opts = opts + opts = opts.any? ? opts.shift : {} + @fail_fast = opts.fetch(:fail_fast, false) + @allow_blank = opts.fetch(:allow_blank, false) end # Validates a given request. diff --git a/spec/grape/request_spec.rb b/spec/grape/request_spec.rb index ee5a43643..1bfce035e 100644 --- a/spec/grape/request_spec.rb +++ b/spec/grape/request_spec.rb @@ -75,7 +75,7 @@ module Grape Grape.config.reset end - subject(:request_params) { Grape::Request.new(env, opts).params } + subject(:request_params) { Grape::Request.new(env, **opts).params } context 'when the API does not include a specific param builder' do let(:opts) { {} } diff --git a/spec/shared/versioning_examples.rb b/spec/shared/versioning_examples.rb index 8e8552070..ebc0742d4 100644 --- a/spec/shared/versioning_examples.rb +++ b/spec/shared/versioning_examples.rb @@ -7,7 +7,7 @@ subject.get :hello do "Version: #{request.env['api.version']}" end - versioned_get '/hello', 'v1', macro_options + versioned_get '/hello', 'v1', **macro_options expect(last_response.body).to eql 'Version: v1' end @@ -18,7 +18,7 @@ subject.get :hello do "Version: #{request.env['api.version']}" end - versioned_get '/hello', 'v1', macro_options.merge(prefix: 'api') + versioned_get '/hello', 'v1', **macro_options.merge(prefix: 'api') expect(last_response.body).to eql 'Version: v1' end @@ -34,14 +34,14 @@ end end - versioned_get '/awesome', 'v1', macro_options + versioned_get '/awesome', 'v1', **macro_options expect(last_response.status).to eql 404 - versioned_get '/awesome', 'v2', macro_options + versioned_get '/awesome', 'v2', **macro_options expect(last_response.status).to eql 200 - versioned_get '/legacy', 'v1', macro_options + versioned_get '/legacy', 'v1', **macro_options expect(last_response.status).to eql 200 - versioned_get '/legacy', 'v2', macro_options + versioned_get '/legacy', 'v2', **macro_options expect(last_response.status).to eql 404 end @@ -51,11 +51,11 @@ 'I exist' end - versioned_get '/awesome', 'v1', macro_options + versioned_get '/awesome', 'v1', **macro_options expect(last_response.status).to eql 200 - versioned_get '/awesome', 'v2', macro_options + versioned_get '/awesome', 'v2', **macro_options expect(last_response.status).to eql 200 - versioned_get '/awesome', 'v3', macro_options + versioned_get '/awesome', 'v3', **macro_options expect(last_response.status).to eql 404 end @@ -74,10 +74,10 @@ end end - versioned_get '/version', 'v2', macro_options + versioned_get '/version', 'v2', **macro_options expect(last_response.status).to eq(200) expect(last_response.body).to eq('v2') - versioned_get '/version', 'v1', macro_options + versioned_get '/version', 'v1', **macro_options expect(last_response.status).to eq(200) expect(last_response.body).to eq('version v1') end @@ -98,11 +98,11 @@ end end - versioned_get '/version', 'v1', macro_options.merge(prefix: subject.prefix) + versioned_get '/version', 'v1', **macro_options.merge(prefix: subject.prefix) expect(last_response.status).to eq(200) expect(last_response.body).to eq('version v1') - versioned_get '/version', 'v2', macro_options.merge(prefix: subject.prefix) + versioned_get '/version', 'v2', **macro_options.merge(prefix: subject.prefix) expect(last_response.status).to eq(200) expect(last_response.body).to eq('v2') end @@ -131,11 +131,11 @@ end end - versioned_get '/version', 'v1', macro_options.merge(prefix: subject.prefix) + versioned_get '/version', 'v1', **macro_options.merge(prefix: subject.prefix) expect(last_response.status).to eq(200) expect(last_response.body).to eq('v1-version') - versioned_get '/version', 'v2', macro_options.merge(prefix: subject.prefix) + versioned_get '/version', 'v2', **macro_options.merge(prefix: subject.prefix) expect(last_response.status).to eq(200) expect(last_response.body).to eq('v2-version') end @@ -148,7 +148,7 @@ subject.get :api_version_with_version_param do params[:version] end - versioned_get '/api_version_with_version_param?version=1', 'v1', macro_options + versioned_get '/api_version_with_version_param?version=1', 'v1', **macro_options expect(last_response.body).to eql '1' end @@ -183,13 +183,13 @@ context 'v1' do it 'finds endpoint' do - versioned_get '/version', 'v1', macro_options + versioned_get '/version', 'v1', **macro_options expect(last_response.status).to eq(200) expect(last_response.body).to eq('v1') end it 'finds catch all' do - versioned_get '/whatever', 'v1', macro_options + versioned_get '/whatever', 'v1', **macro_options expect(last_response.status).to eq(200) expect(last_response.body).to end_with 'whatever' end @@ -197,13 +197,13 @@ context 'v2' do it 'finds endpoint' do - versioned_get '/version', 'v2', macro_options + versioned_get '/version', 'v2', **macro_options expect(last_response.status).to eq(200) expect(last_response.body).to eq('v2') end it 'finds catch all' do - versioned_get '/whatever', 'v2', macro_options + versioned_get '/whatever', 'v2', **macro_options expect(last_response.status).to eq(200) expect(last_response.body).to end_with 'whatever' end diff --git a/spec/support/versioned_helpers.rb b/spec/support/versioned_helpers.rb index 8d7c07f18..ea78013d4 100644 --- a/spec/support/versioned_helpers.rb +++ b/spec/support/versioned_helpers.rb @@ -44,7 +44,7 @@ def versioned_headers(**options) end def versioned_get(path, version_name, **version_options) - path = versioned_path(version_options.merge(version: version_name, path: path)) + path = versioned_path(**version_options.merge(version: version_name, path: path)) headers = versioned_headers(**version_options.merge(version: version_name)) params = {} params = { version_options[:parameter] => version_name } if version_options[:using] == :param From da1fe0218b054f4b3f03a217ccbfe0dd489ae606 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Tue, 29 Dec 2020 17:58:42 +0100 Subject: [PATCH 276/290] Add ruby 3.0 in GH Actions --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8782c8247..95b941260 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: - 2.5 - 2.6 - 2.7 + - 3.0 gemfile: - Gemfile - gemfiles/rack1.gemfile @@ -36,8 +37,15 @@ jobs: - gemfiles/rails_edge.gemfile - gemfiles/rails_5.gemfile - gemfiles/rails_6.gemfile + - gemfiles/rails_6_1.gemfile experimental: [false] include: + - ruby: 3.0 + gemfile: 'gemfiles/multi_json.gemfile' + experimental: false + - ruby: 3.0 + gemfile: 'gemfiles/multi_xml.gemfile' + experimental: false - ruby: 2.7 gemfile: 'gemfiles/multi_json.gemfile' experimental: false From a09efb0be8d77df13dde1296613da03bf9a039d8 Mon Sep 17 00:00:00 2001 From: Eric Proulx Date: Tue, 29 Dec 2020 18:06:02 +0100 Subject: [PATCH 277/290] Remove @opts --- lib/grape/validations/validators/base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index f45a254ca..f8a2a1bcf 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -18,7 +18,6 @@ def initialize(attrs, options, required, scope, *opts) @option = options @required = required @scope = scope - @opts = opts opts = opts.any? ? opts.shift : {} @fail_fast = opts.fetch(:fail_fast, false) @allow_blank = opts.fetch(:allow_blank, false) From 501a76ee3d2c51a53066995b5ae63c8b6aaf2da1 Mon Sep 17 00:00:00 2001 From: yanpz Date: Thu, 14 Jan 2021 16:28:32 +0800 Subject: [PATCH 278/290] Update doc ConnectionManagement without rails --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e3a1dd6c..2f9a0bae6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ - [All](#all) - [Rack](#rack) - [ActiveRecord without Rails](#activerecord-without-rails) + - [Rails 4](#rails-4) + - [Rails 5+](#rails-5) - [Alongside Sinatra (or other frameworks)](#alongside-sinatra-or-other-frameworks) - [Rails](#rails) - [Rails < 5.2](#rails--52) @@ -317,13 +319,21 @@ Grape will also automatically respond to HEAD and OPTIONS for all GET, and just If you want to use ActiveRecord within Grape, you will need to make sure that ActiveRecord's connection pool is handled correctly. +#### Rails 4 + The easiest way to achieve that is by using ActiveRecord's `ConnectionManagement` middleware in your `config.ru` before mounting Grape, e.g.: ```ruby use ActiveRecord::ConnectionAdapters::ConnectionManagement +``` -run Twitter::API +#### Rails 5+ + +Use [otr-activerecord](https://github.com/jhollinger/otr-activerecord) as follows: + +```ruby +use OTR::ActiveRecord::ConnectionManagement ``` ### Alongside Sinatra (or other frameworks) From fef726931e64e3938e013c34914b1f3fcbbfd57d Mon Sep 17 00:00:00 2001 From: dblock Date: Tue, 19 Jan 2021 10:18:07 -0500 Subject: [PATCH 279/290] Use raw DANGER_GITHUB_API_TOKEN. --- .github/workflows/danger.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index 681f52b46..040beda21 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -1,8 +1,8 @@ --- -name: Danger +name: danger on: pull_request: - ypes: [opened, reopened, edited, synchronize] + types: [opened, reopened, edited, synchronize] jobs: danger: runs-on: ubuntu-20.04 @@ -14,6 +14,8 @@ jobs: ruby-version: 2.6 bundler-cache: true - name: Run Danger - run: bundle exec danger - env: - DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # the token is public, this is ok + TOKEN='b8b19daa0ade737762c' + TOKEN+='f35edcb328642d371ce86' + DANGER_GITHUB_API_TOKEN=$TOKEN bundle exec danger --verbose From e7f771e63359e54e2cb0feb7ff52b9746fe13e05 Mon Sep 17 00:00:00 2001 From: Fernando Sainz <167863+fsainz@users.noreply.github.com> Date: Tue, 26 Jan 2021 15:18:16 +0100 Subject: [PATCH 280/290] fixes configuration inside namespaced params scope (#2152) --- CHANGELOG.md | 1 + lib/grape/validations/params_scope.rb | 2 +- spec/grape/api_remount_spec.rb | 13 +++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad0462bf9..ad32abbc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * [#2137](https://github.com/ruby-grape/grape/pull/2137): Fix typos - [@johnny-miyake](https://github.com/johnny-miyake). * [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). * [#2132](https://github.com/ruby-grape/grape/pull/2132): Use #ruby2_keywords for correct delegation on Ruby <= 2.6, 2.7 and 3 - [@eregon](https://github.com/eregon). +* [#2152](https://github.com/ruby-grape/grape/pull/2152): Fix configuration method inside namespaced params - [@fsainz](https://github.com/fsainz). ### 1.5.1 (2020/11/15) diff --git a/lib/grape/validations/params_scope.rb b/lib/grape/validations/params_scope.rb index 792762b06..a39383d5d 100644 --- a/lib/grape/validations/params_scope.rb +++ b/lib/grape/validations/params_scope.rb @@ -39,7 +39,7 @@ def initialize(opts, &block) end def configuration - @api.configuration.evaluate + @api.configuration.respond_to?(:evaluate) ? @api.configuration.evaluate : @api.configuration end # @return [Boolean] whether or not this entire scope needs to be diff --git a/spec/grape/api_remount_spec.rb b/spec/grape/api_remount_spec.rb index 2f7501765..ff764293c 100644 --- a/spec/grape/api_remount_spec.rb +++ b/spec/grape/api_remount_spec.rb @@ -340,19 +340,24 @@ def app context 'when the configuration is read within a namespace' do before do a_remounted_api.namespace 'api' do + params do + requires configuration[:required_param] + end get "/#{configuration[:path]}" do '10 votes' end end - root_api.mount a_remounted_api, with: { path: 'votes' } - root_api.mount a_remounted_api, with: { path: 'scores' } + root_api.mount a_remounted_api, with: { path: 'votes', required_param: 'param_key' } + root_api.mount a_remounted_api, with: { path: 'scores', required_param: 'param_key' } end it 'will use the dynamic configuration on all routes' do - get 'api/votes' + get 'api/votes', param_key: 'a' expect(last_response.body).to eql '10 votes' - get 'api/scores' + get 'api/scores', param_key: 'a' expect(last_response.body).to eql '10 votes' + get 'api/votes' + expect(last_response.status).to eq 400 end end From 54b2e116574a3fd105088e3dc1db06abf40c383c Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Wed, 27 Jan 2021 19:37:15 +0200 Subject: [PATCH 281/290] custom types can set a message to be used in the response when invalid Just return an instance of `Grape::Types::InvalidValue` with the message: class Color def self.parse(value) return value if %w[blue red green].include?(value) Grape::Types::InvalidValue.new('Invalid color') end end Any raised exception will be treated as an invalid value as it was before. --- CHANGELOG.md | 1 + Gemfile | 4 ++ README.md | 8 +-- lib/grape/validations/types.rb | 5 +- .../validations/types/custom_type_coercer.rb | 2 + lib/grape/validations/types/invalid_value.rb | 24 +++++++++ lib/grape/validations/validators/coerce.rb | 9 ++-- .../validations/validators/coerce_spec.rb | 52 ++++++++++++++----- 8 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 lib/grape/validations/types/invalid_value.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index ad32abbc9..6ca67c53a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ #### Features * Your contribution here. +* [#2157](https://github.com/ruby-grape/grape/pull/2157): Custom types can set a message to be used in the response when invalid - [@dnesteryuk](https://github.com/dnesteryuk). * [#2145](https://github.com/ruby-grape/grape/pull/2145): Ruby 3.0 compatibility - [@ericproulx](https://github.com/ericproulx). * [#2143](https://github.com/ruby-grape/grape/pull/2143): Enable GitHub Actions with updated RuboCop and Danger - [@anakinj](https://github.com/anakinj). diff --git a/Gemfile b/Gemfile index 6c4124a52..8e32048c4 100644 --- a/Gemfile +++ b/Gemfile @@ -35,3 +35,7 @@ group :test do gem 'rspec', '~> 3.0' gem 'ruby-grape-danger', '~> 0.2.0', require: false end + +platforms :jruby do + gem 'racc' +end diff --git a/README.md b/README.md index 2f9a0bae6..224711058 100644 --- a/README.md +++ b/README.md @@ -1183,7 +1183,8 @@ Aside from the default set of supported types listed above, any class can be used as a type as long as an explicit coercion method is supplied. If the type implements a class-level `parse` method, Grape will use it automatically. This method must take one string argument and return an instance of the correct -type, or raise an exception to indicate the value was invalid. E.g., +type, or return an instance of `Grape::Types::InvalidValue` which optionally +accepts a message to be returned in the response. ```ruby class Color @@ -1193,8 +1194,9 @@ class Color end def self.parse(value) - fail 'Invalid color' unless %w(blue red green).include?(value) - new(value) + return new(value) if %w[blue red green]).include?(value) + + Grape::Types::InvalidValue.new('Unsupported color') end end diff --git a/lib/grape/validations/types.rb b/lib/grape/validations/types.rb index a682286a5..2240a7fa7 100644 --- a/lib/grape/validations/types.rb +++ b/lib/grape/validations/types.rb @@ -7,6 +7,7 @@ require_relative 'types/variant_collection_coercer' require_relative 'types/json' require_relative 'types/file' +require_relative 'types/invalid_value' module Grape module Validations @@ -21,10 +22,6 @@ module Validations # and {Grape::Dsl::Parameters#optional}. The main # entry point for this process is {Types.build_coercer}. module Types - # Instances of this class may be used as tokens to denote that - # a parameter value could not be coerced. - class InvalidValue; end - # Types representing a single value, which are coerced. PRIMITIVES = [ # Numerical diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index a11403e1c..f1fd4a80c 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -55,6 +55,8 @@ def call(val) return if val.nil? coerced_val = @method.call(val) + + return coerced_val if coerced_val.is_a?(InvalidValue) return InvalidValue.new unless coerced?(coerced_val) coerced_val end diff --git a/lib/grape/validations/types/invalid_value.rb b/lib/grape/validations/types/invalid_value.rb new file mode 100644 index 000000000..5c566a642 --- /dev/null +++ b/lib/grape/validations/types/invalid_value.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Grape + module Validations + module Types + # Instances of this class may be used as tokens to denote that a parameter value could not be + # coerced. The given message will be used as a validation error. + class InvalidValue + attr_reader :message + + def initialize(message = nil) + @message = message + end + end + end + end +end + +# only exists to make it shorter for external use +module Grape + module Types + InvalidValue = Class.new(Grape::Validations::Types::InvalidValue) + end +end diff --git a/lib/grape/validations/validators/coerce.rb b/lib/grape/validations/validators/coerce.rb index 1b15c069d..f79b4fa6e 100644 --- a/lib/grape/validations/validators/coerce.rb +++ b/lib/grape/validations/validators/coerce.rb @@ -36,7 +36,7 @@ def validate_param!(attr_name, params) new_value = coerce_value(params[attr_name]) - raise validation_exception(attr_name) unless valid_type?(new_value) + raise validation_exception(attr_name, new_value.message) unless valid_type?(new_value) # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash # which looses wrappers for hashes and arrays after reassigning values @@ -80,8 +80,11 @@ def type @option[:type].is_a?(Hash) ? @option[:type][:value] : @option[:type] end - def validation_exception(attr_name) - Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:coerce)) + def validation_exception(attr_name, custom_msg = nil) + Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: custom_msg || message(:coerce) + ) end end end diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index e157748e9..a9d3123a9 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -227,23 +227,51 @@ def self.parsed?(value) expect(last_response.body).to eq('NilClass') end - it 'is a custom type' do - subject.params do - requires :uri, coerce: SecureURIOnly - end - subject.get '/secure_uri' do - params[:uri].class + context 'a custom type' do + it 'coerces the given value' do + subject.params do + requires :uri, coerce: SecureURIOnly + end + subject.get '/secure_uri' do + params[:uri].class + end + + get 'secure_uri', uri: 'https://www.example.com' + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('URI::HTTPS') + + get 'secure_uri', uri: 'http://www.example.com' + + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('uri is invalid') end - get 'secure_uri', uri: 'https://www.example.com' + context 'returning the InvalidValue instance when invalid' do + let(:custom_type) do + Class.new do + def self.parse(_val) + Grape::Types::InvalidValue.new('must be unique') + end + end + end - expect(last_response.status).to eq(200) - expect(last_response.body).to eq('URI::HTTPS') + it 'uses a custom message added to the invalid value' do + type = custom_type + + subject.params do + requires :name, type: type + end + subject.get '/whatever' do + params[:name].class + end - get 'secure_uri', uri: 'http://www.example.com' + get 'whatever', name: 'Bob' - expect(last_response.status).to eq(400) - expect(last_response.body).to eq('uri is invalid') + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('name must be unique') + end + end end context 'Array' do From 6cb8eaf65c352fb675720fd6a8ff1c96fea7aca8 Mon Sep 17 00:00:00 2001 From: Matias Asis Date: Tue, 2 Feb 2021 00:10:25 -0300 Subject: [PATCH 282/290] spelling corrections --- CHANGELOG.md | 2 +- lib/grape/api.rb | 2 +- lib/grape/dsl/callbacks.rb | 2 +- lib/grape/dsl/inside_route.rb | 2 +- lib/grape/dsl/parameters.rb | 2 +- lib/grape/dsl/routing.rb | 4 ++-- lib/grape/exceptions/validation.rb | 2 +- spec/grape/entity_spec.rb | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ca67c53a..cd1a2ca6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -639,7 +639,7 @@ * [#492](https://github.com/ruby-grape/grape/pull/492): Don't allow to have nil value when a param is required and has a list of allowed values - [@Antti](https://github.com/Antti). * [#495](https://github.com/ruby-grape/grape/pull/495): Fixed `ParamsScope#params` for parameters nested inside arrays - [@asross](https://github.com/asross). * [#498](https://github.com/ruby-grape/grape/pull/498): Dry'ed up options and headers logic, allow headers to be passed to OPTIONS requests - [@karlfreeman](https://github.com/karlfreeman). -* [#500](https://github.com/ruby-grape/grape/pull/500): Skip entity auto-detection when explicitely passed - [@yaneq](https://github.com/yaneq). +* [#500](https://github.com/ruby-grape/grape/pull/500): Skip entity auto-detection when explicitly passed - [@yaneq](https://github.com/yaneq). * [#503](https://github.com/ruby-grape/grape/pull/503): Calling declared(params) from child namespace fails to include parent namespace defined params - [@myitcv](https://github.com/myitcv). * [#512](https://github.com/ruby-grape/grape/pull/512): Don't create `Grape::Request` multiple times - [@dblock](https://github.com/dblock). * [#538](https://github.com/ruby-grape/grape/pull/538): Fixed default values for grouped params - [@dm1try](https://github.com/dm1try). diff --git a/lib/grape/api.rb b/lib/grape/api.rb index dd8ae8d89..219538292 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -87,7 +87,7 @@ def const_missing(*args) end # The remountable class can have a configuration hash to provide some dynamic class-level variables. - # For instance, a descripcion could be done using: `desc configuration[:description]` if it may vary + # For instance, a description could be done using: `desc configuration[:description]` if it may vary # depending on where the endpoint is mounted. Use with care, if you find yourself using configuration # too much, you may actually want to provide a new API rather than remount it. def mount_instance(**opts) diff --git a/lib/grape/dsl/callbacks.rb b/lib/grape/dsl/callbacks.rb index ae6049aa2..03827684d 100644 --- a/lib/grape/dsl/callbacks.rb +++ b/lib/grape/dsl/callbacks.rb @@ -59,7 +59,7 @@ def after(&block) # end # end # - # This will make sure that the ApiLogger is opened and close around every + # This will make sure that the ApiLogger is opened and closed around every # request # @param ensured_block [Proc] The block to be executed after every api_call def finally(&block) diff --git a/lib/grape/dsl/inside_route.rb b/lib/grape/dsl/inside_route.rb index 134f1ea24..d12107199 100644 --- a/lib/grape/dsl/inside_route.rb +++ b/lib/grape/dsl/inside_route.rb @@ -399,7 +399,7 @@ def entity_class_for_obj(object, options) entity_class = options.delete(:with) if entity_class.nil? - # entity class not explicitely defined, auto-detect from relation#klass or first object in the collection + # entity class not explicitly defined, auto-detect from relation#klass or first object in the collection object_class = if object.respond_to?(:klass) object.klass else diff --git a/lib/grape/dsl/parameters.rb b/lib/grape/dsl/parameters.rb index b448ca52a..84a0fa574 100644 --- a/lib/grape/dsl/parameters.rb +++ b/lib/grape/dsl/parameters.rb @@ -72,7 +72,7 @@ def use(*names) # Require one or more parameters for the current endpoint. # - # @param attrs list of parameter names, or, if :using is + # @param attrs list of parameters names, or, if :using is # passed as an option, which keys to include (:all or :none) from # the :using hash. The last key can be a hash, which specifies # options for the parameters diff --git a/lib/grape/dsl/routing.rb b/lib/grape/dsl/routing.rb index 61bf1f21a..7f1e78c08 100644 --- a/lib/grape/dsl/routing.rb +++ b/lib/grape/dsl/routing.rb @@ -152,7 +152,7 @@ def route(methods, paths = ['/'], route_options = {}, &block) end # Declare a "namespace", which prefixes all subordinate routes with its - # name. Any endpoints within a namespace, or group, resource, segment, + # name. Any endpoints within a namespace, group, resource or segment, # etc., will share their parent context as well as any configuration # done in the namespace context. # @@ -200,7 +200,7 @@ def reset_endpoints! @endpoints = [] end - # Thie method allows you to quickly define a parameter route segment + # This method allows you to quickly define a parameter route segment # in your API. # # @param param [Symbol] The name of the parameter you wish to declare. diff --git a/lib/grape/exceptions/validation.rb b/lib/grape/exceptions/validation.rb index bfd1dee2d..6b98112ab 100644 --- a/lib/grape/exceptions/validation.rb +++ b/lib/grape/exceptions/validation.rb @@ -17,7 +17,7 @@ def initialize(params:, message: nil, **args) super(**args) end - # remove all the unnecessary stuff from Grape::Exceptions::Base like status + # Remove all the unnecessary stuff from Grape::Exceptions::Base like status # and headers when converting a validation error to json or string def as_json(*_args) to_s diff --git a/spec/grape/entity_spec.rb b/spec/grape/entity_spec.rb index add8461be..beb304061 100644 --- a/spec/grape/entity_spec.rb +++ b/spec/grape/entity_spec.rb @@ -116,7 +116,7 @@ def first expect(last_response.body).to eq('Auto-detect!') end - it 'does not run autodetection for Entity when explicitely provided' do + it 'does not run autodetection for Entity when explicitly provided' do entity = Class.new(Grape::Entity) some_array = [] From 381c00e14d29b45edd61c4f1d3a5f68fe5fbbf36 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 6 Feb 2021 12:56:54 +0200 Subject: [PATCH 283/290] Preparing for release, 1.5.2 --- CHANGELOG.md | 4 +--- README.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd1a2ca6c..d75cccf98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,13 @@ -### 1.5.2 (Next) +### 1.5.2 (2021/02/06) #### Features -* Your contribution here. * [#2157](https://github.com/ruby-grape/grape/pull/2157): Custom types can set a message to be used in the response when invalid - [@dnesteryuk](https://github.com/dnesteryuk). * [#2145](https://github.com/ruby-grape/grape/pull/2145): Ruby 3.0 compatibility - [@ericproulx](https://github.com/ericproulx). * [#2143](https://github.com/ruby-grape/grape/pull/2143): Enable GitHub Actions with updated RuboCop and Danger - [@anakinj](https://github.com/anakinj). #### Fixes -* Your contribution here. * [#2144](https://github.com/ruby-grape/grape/pull/2144): Fix compatibility issue with activesupport 6.1 and XML serialization of arrays - [@anakinj](https://github.com/anakinj). * [#2137](https://github.com/ruby-grape/grape/pull/2137): Fix typos - [@johnny-miyake](https://github.com/johnny-miyake). * [#2131](https://github.com/ruby-grape/grape/pull/2131): Fix Ruby 2.7 keyword deprecation warning in validators/coerce - [@K0H205](https://github.com/K0H205). diff --git a/README.md b/README.md index 224711058..0f967f65b 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,7 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.5.2**. -Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.5.1](https://github.com/ruby-grape/grape/blob/v1.5.1/README.md). +You're reading the documentation for the stable release of Grape, 1.5.2. ## Project Resources From 0e0ac104c51ce5e70e204ce51965d231f1a6d216 Mon Sep 17 00:00:00 2001 From: Dmitriy Nesteryuk Date: Sat, 6 Feb 2021 12:59:08 +0200 Subject: [PATCH 284/290] Preparing for next development iteration, 1.5.3 --- CHANGELOG.md | 10 ++++++++++ README.md | 4 +++- lib/grape/version.rb | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d75cccf98..279349688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### 1.5.3 (Next) + +#### Features + +* Your contribution here. + +#### Fixes + +* Your contribution here. + ### 1.5.2 (2021/02/06) #### Features diff --git a/README.md b/README.md index 0f967f65b..373d53885 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,9 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the stable release of Grape, 1.5.2. +You're reading the documentation for the next release of Grape, which should be **1.5.3**. +Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. +The current stable release is [1.5.2](https://github.com/ruby-grape/grape/blob/v1.5.2/README.md). ## Project Resources diff --git a/lib/grape/version.rb b/lib/grape/version.rb index b62f63d74..9c0689139 100644 --- a/lib/grape/version.rb +++ b/lib/grape/version.rb @@ -2,5 +2,5 @@ module Grape # The current version of Grape. - VERSION = '1.5.2' + VERSION = '1.5.3' end From 7c524c3e8d8bb864dc12e8539b75a5958c97418c Mon Sep 17 00:00:00 2001 From: Joakim Antman Date: Tue, 9 Feb 2021 12:06:07 +0200 Subject: [PATCH 285/290] Fix testing to be compatible with Rails 7 dependencies --- .github/workflows/test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95b941260..188e01c6d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,6 @@ jobs: - gemfiles/rack1.gemfile - gemfiles/rack2.gemfile - gemfiles/rack_edge.gemfile - - gemfiles/rails_edge.gemfile - gemfiles/rails_5.gemfile - gemfiles/rails_6.gemfile - gemfiles/rails_6_1.gemfile @@ -52,6 +51,9 @@ jobs: - ruby: 2.7 gemfile: 'gemfiles/multi_xml.gemfile' experimental: false + - ruby: 2.7 + gemfile: 'gemfiles/rails_edge.gemfile' + experimental: false - ruby: "ruby-head" experimental: true - ruby: "truffleruby-head" From 6a21f8080056593cfb5cc3eebc2a5985d7618fec Mon Sep 17 00:00:00 2001 From: Ben Schmeckpeper Date: Wed, 10 Feb 2021 10:41:48 -0600 Subject: [PATCH 286/290] Handle EOFError raised by Rack (#2161) --- .github/workflows/test.yml | 5 +-- CHANGELOG.md | 2 ++ gemfiles/rack2_2.gemfile | 39 ++++++++++++++++++++++ lib/grape.rb | 1 + lib/grape/exceptions/empty_message_body.rb | 11 ++++++ lib/grape/locale/en.yml | 2 +- lib/grape/request.rb | 2 ++ spec/grape/endpoint_spec.rb | 13 ++++++++ 8 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 gemfiles/rack2_2.gemfile create mode 100644 lib/grape/exceptions/empty_message_body.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 188e01c6d..44a7541f9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,7 @@ jobs: - Gemfile - gemfiles/rack1.gemfile - gemfiles/rack2.gemfile + - gemfiles/rack2_2.gemfile - gemfiles/rack_edge.gemfile - gemfiles/rails_5.gemfile - gemfiles/rails_6.gemfile @@ -76,7 +77,7 @@ jobs: - name: Run tests run: bundle exec rake spec - + - name: Run tests (spec/integration/eager_load) if: ${{ matrix.gemfile == 'Gemfile' }} run: bundle exec rspec spec/integration/eager_load @@ -84,7 +85,7 @@ jobs: - name: Run tests (spec/integration/multi_json) if: ${{ matrix.gemfile == 'gemfiles/multi_json.gemfile' }} run: bundle exec rspec spec/integration/multi_json - + - name: Run tests (spec/integration/multi_xml) if: ${{ matrix.gemfile == 'gemfiles/multi_xml.gemfile' }} run: bundle exec rspec spec/integration/multi_xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 279349688..b610254e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * Your contribution here. +* [#2161](https://github.com/ruby-grape/grape/pull/2157): Handle EOFError from Rack when given an empty multipart body - [@bschmeck](https://github.com/bschmeck). + ### 1.5.2 (2021/02/06) #### Features diff --git a/gemfiles/rack2_2.gemfile b/gemfiles/rack2_2.gemfile new file mode 100644 index 000000000..88cfa240d --- /dev/null +++ b/gemfiles/rack2_2.gemfile @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +# This file was generated by Appraisal + +source 'https://rubygems.org' + +gem 'rack', '~> 2.2' + +group :development, :test do + gem 'bundler' + gem 'hashie' + gem 'rake' + gem 'rubocop', '1.7.0' + gem 'rubocop-ast', '1.3.0' + gem 'rubocop-performance', '1.9.1', require: false +end + +group :development do + gem 'appraisal' + gem 'benchmark-ips' + gem 'benchmark-memory' + gem 'guard' + gem 'guard-rspec' + gem 'guard-rubocop' +end + +group :test do + gem 'cookiejar' + gem 'coveralls_reborn' + gem 'grape-entity', '~> 0.6' + gem 'maruku' + gem 'mime-types' + gem 'rack-jsonp', require: 'rack/jsonp' + gem 'rack-test', '~> 1.1.0' + gem 'rspec', '~> 3.0' + gem 'ruby-grape-danger', '~> 0.2.0', require: false +end + +gemspec path: '../' diff --git a/lib/grape.rb b/lib/grape.rb index c1f313c2f..818c8f6bc 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -76,6 +76,7 @@ module Exceptions autoload :InvalidVersionHeader autoload :MethodNotAllowed autoload :InvalidResponse + autoload :EmptyMessageBody end end diff --git a/lib/grape/exceptions/empty_message_body.rb b/lib/grape/exceptions/empty_message_body.rb new file mode 100644 index 000000000..c4fd43176 --- /dev/null +++ b/lib/grape/exceptions/empty_message_body.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Grape + module Exceptions + class EmptyMessageBody < Base + def initialize(body_format) + super(message: compose_message(:empty_message_body, body_format: body_format), status: 400) + end + end + end +end diff --git a/lib/grape/locale/en.yml b/lib/grape/locale/en.yml index 10fa381f7..036eb93b8 100644 --- a/lib/grape/locale/en.yml +++ b/lib/grape/locale/en.yml @@ -44,6 +44,7 @@ en: "when specifying %{body_format} as content-type, you must pass valid %{body_format} in the request's 'body' " + empty_message_body: 'Empty message body supplied with %{body_format} content-type' invalid_accept_header: problem: 'Invalid accept header' resolution: '%{message}' @@ -51,4 +52,3 @@ en: problem: 'Invalid version header' resolution: '%{message}' invalid_response: 'Invalid response' - diff --git a/lib/grape/request.rb b/lib/grape/request.rb index b54779748..0357477d5 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -15,6 +15,8 @@ def initialize(env, **options) def params @params ||= build_params + rescue EOFError + raise Grape::Exceptions::EmptyMessageBody, content_type end def headers diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 4bbeb070c..487fbb0d3 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -420,6 +420,19 @@ def app expect(last_response.status).to eq(201) expect(last_response.body).to eq('Bob') end + + # Rack swallowed this error until v2.2.0 + it 'returns a 400 if given an invalid multipart body', if: Gem::Version.new(Rack.release) >= Gem::Version.new('2.2.0') do + subject.params do + requires :file, type: Rack::Multipart::UploadedFile + end + subject.post '/upload' do + params[:file][:filename] + end + post '/upload', { file: '' }, 'CONTENT_TYPE' => 'multipart/form-data; boundary=foobar' + expect(last_response.status).to eq(400) + expect(last_response.body).to eq('Empty message body supplied with multipart/form-data; boundary=foobar content-type') + end end it 'responds with a 415 for an unsupported content-type' do From c859eeee2f7b207c973e0156c938e62931a8a12e Mon Sep 17 00:00:00 2001 From: Hermann Mayer Date: Fri, 19 Feb 2021 20:19:36 +0100 Subject: [PATCH 287/290] Corrected a hash modification while iterating issue. Signed-off-by: Hermann Mayer --- CHANGELOG.md | 1 + lib/grape/api.rb | 2 +- spec/grape/api_spec.rb | 77 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b610254e7..760e2a123 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Your contribution here. * [#2161](https://github.com/ruby-grape/grape/pull/2157): Handle EOFError from Rack when given an empty multipart body - [@bschmeck](https://github.com/bschmeck). +* [#2162](https://github.com/ruby-grape/grape/pull/2162): Corrected a hash modification while iterating issue - [@Jack12816](https://github.com/Jack12816). ### 1.5.2 (2021/02/06) diff --git a/lib/grape/api.rb b/lib/grape/api.rb index 219538292..11bcb6a44 100644 --- a/lib/grape/api.rb +++ b/lib/grape/api.rb @@ -141,7 +141,7 @@ def instance_for_rack # Adds a new stage to the set up require to get a Grape::API up and running def add_setup(method, *args, &block) setup_step = { method: method, args: args, block: block } - @setup << setup_step + @setup += [setup_step] last_response = nil @instances.each do |instance| last_response = replay_step_on(instance, setup_step) diff --git a/spec/grape/api_spec.rb b/spec/grape/api_spec.rb index 0d722a4b1..0cb7e1251 100644 --- a/spec/grape/api_spec.rb +++ b/spec/grape/api_spec.rb @@ -4056,4 +4056,81 @@ def before expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/) end end + + describe 'custom route helpers on nested APIs' do + let(:shared_api_module) do + Module.new do + # rubocop:disable Style/ExplicitBlockArgument because this causes + # the underlying issue in this form + def uniqe_id_route + params do + use :unique_id + end + route_param(:id) do + yield + end + end + # rubocop:enable Style/ExplicitBlockArgument + end + end + let(:shared_api_definitions) do + Module.new do + extend ActiveSupport::Concern + + included do + helpers do + params :unique_id do + requires :id, type: String, + allow_blank: false, + regexp: /\d+-\d+/ + end + end + end + end + end + let(:orders_root) do + shared = shared_api_definitions + find = orders_find_endpoint + Class.new(Grape::API) do + include shared + + namespace(:orders) do + mount find + end + end + end + let(:orders_find_endpoint) do + shared = shared_api_definitions + Class.new(Grape::API) do + include shared + + uniqe_id_route do + desc 'Fetch a single order' do + detail 'While specifying the order id on the route' + end + get { params[:id] } + end + end + end + subject(:grape_api) do + Class.new(Grape::API) do + version 'v1', using: :path + end + end + + before do + Grape::API::Instance.extend(shared_api_module) + subject.mount orders_root + end + + it 'returns an error when the id is bad' do + get '/v1/orders/abc' + expect(last_response.body).to be_eql('id is invalid') + end + + it 'returns the given id when it is valid' do + get '/v1/orders/1-2' + expect(last_response.body).to be_eql('1-2') + end + end end From e0f412ca73f6188c233ac9c35661027d28f38a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gw=C3=A9na=C3=ABl=20Rault?= Date: Fri, 5 Mar 2021 15:58:34 +0100 Subject: [PATCH 288/290] coerce_with should be called for params with nil value (#2164) --- CHANGELOG.md | 1 + README.md | 1 + UPGRADING.md | 27 +++++++ .../validations/types/custom_type_coercer.rb | 2 - .../validations/validators/coerce_spec.rb | 76 +++++++++++++++++++ 5 files changed, 105 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 760e2a123..480c27f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * [#2161](https://github.com/ruby-grape/grape/pull/2157): Handle EOFError from Rack when given an empty multipart body - [@bschmeck](https://github.com/bschmeck). * [#2162](https://github.com/ruby-grape/grape/pull/2162): Corrected a hash modification while iterating issue - [@Jack12816](https://github.com/Jack12816). +* [#2164](https://github.com/ruby-grape/grape/pull/2164): Fix: `coerce_with` is now called for params with `nil` value - [@braktar](https://github.com/braktar). ### 1.5.2 (2021/02/06) diff --git a/README.md b/README.md index 373d53885..e7f9a14b1 100644 --- a/README.md +++ b/README.md @@ -1228,6 +1228,7 @@ params do end end ``` +Note that, a `nil` value will call the custom coercion method, while a missing parameter will not. Example of use of `coerce_with` with a lambda (a class with a `parse` method could also have been used) It will parse a string and return an Array of Integers, matching the `Array[Integer]` `type`. diff --git a/UPGRADING.md b/UPGRADING.md index e5b6947fa..c8813e32f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,33 @@ Upgrading Grape =============== + +### Upgrading to >= 1.5.3 + +### Nil value and coercion + +Prior to 1.2.5 version passing a `nil` value for a parameter with a custom coercer would invoke the coercer, and not passing a parameter would not invoke it. +This behavior was not tested or documented. Version 1.3.0 quietly changed this behavior, in such that `nil` values skipped the coercion. Version 1.5.3 fixes and documents this as follows: + +```ruby +class Api < Grape::API + params do + optional :value, type: Integer, coerce_with: ->(val) { val || 0 } + end + + get 'example' do + params[:my_param] + end + get '/example', params: { value: nil } + # 1.5.2 = nil + # 1.5.3 = 0 + get '/example', params: {} + # 1.5.2 = nil + # 1.5.3 = nil +end +``` +See [#2164](https://github.com/ruby-grape/grape/pull/2164) for more information. + ### Upgrading to >= 1.5.1 #### Dependent params diff --git a/lib/grape/validations/types/custom_type_coercer.rb b/lib/grape/validations/types/custom_type_coercer.rb index f1fd4a80c..be72aff56 100644 --- a/lib/grape/validations/types/custom_type_coercer.rb +++ b/lib/grape/validations/types/custom_type_coercer.rb @@ -52,8 +52,6 @@ def initialize(type, method = nil) # this should always be a string. # @return [Object] the coerced result def call(val) - return if val.nil? - coerced_val = @method.call(val) return coerced_val if coerced_val.is_a?(InvalidValue) diff --git a/spec/grape/validations/validators/coerce_spec.rb b/spec/grape/validations/validators/coerce_spec.rb index a9d3123a9..3f8046f8a 100644 --- a/spec/grape/validations/validators/coerce_spec.rb +++ b/spec/grape/validations/validators/coerce_spec.rb @@ -706,6 +706,44 @@ def self.parse(_val) expect(JSON.parse(last_response.body)).to eq([1, 1, 1, 1]) end + context 'Array type and coerce_with should' do + before do + subject.params do + optional :arr, type: Array, coerce_with: (lambda do |val| + if val.nil? + [] + else + val + end + end) + end + subject.get '/' do + params[:arr].class.to_s + end + end + + it 'coerce nil value to array' do + get '/', arr: nil + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('Array') + end + + it 'not coerce missing field' do + get '/' + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + + it 'coerce array as array' do + get '/', arr: [] + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('Array') + end + end + it 'uses parse where available' do subject.params do requires :ints, type: Array, coerce_with: JSON do @@ -754,6 +792,44 @@ def self.parse(_val) expect(last_response.body).to eq('3') end + context 'Integer type and coerce_with should' do + before do + subject.params do + optional :int, type: Integer, coerce_with: (lambda do |val| + if val.nil? + 0 + else + val.to_i + end + end) + end + subject.get '/' do + params[:int].class.to_s + end + end + + it 'coerce nil value to integer' do + get '/', int: nil + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('Integer') + end + + it 'not coerce missing field' do + get '/' + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('NilClass') + end + + it 'coerce integer as integer' do + get '/', int: 1 + + expect(last_response.status).to eq(200) + expect(last_response.body).to eq('Integer') + end + end + context 'Integer type and coerce_with potentially returning nil' do before do subject.params do From 4e8abf0e95ba86cb033c98041a288c7ea494de62 Mon Sep 17 00:00:00 2001 From: Brandon Fish Date: Fri, 5 Mar 2021 16:34:39 -0600 Subject: [PATCH 289/290] Initialize error data using constructors directly --- lib/grape/endpoint.rb | 2 +- lib/grape/parser/json.rb | 2 +- lib/grape/parser/xml.rb | 2 +- lib/grape/request.rb | 2 +- lib/grape/validations/validators/base.rb | 2 +- lib/grape/validations/validators/multiple_params_base.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 7a8439692..068e1ca0a 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -255,7 +255,7 @@ def run run_filters befores, :before if (allowed_methods = env[Grape::Env::GRAPE_ALLOWED_METHODS]) - raise Grape::Exceptions::MethodNotAllowed, header.merge('Allow' => allowed_methods) unless options? + raise Grape::Exceptions::MethodNotAllowed.new(header.merge('Allow' => allowed_methods)) unless options? header 'Allow', allowed_methods response_object = '' status 204 diff --git a/lib/grape/parser/json.rb b/lib/grape/parser/json.rb index 7f72c9a94..4e665a1ec 100644 --- a/lib/grape/parser/json.rb +++ b/lib/grape/parser/json.rb @@ -8,7 +8,7 @@ def call(object, _env) ::Grape::Json.load(object) rescue ::Grape::Json::ParseError # handle JSON parsing errors via the rescue handlers or provide error message - raise Grape::Exceptions::InvalidMessageBody, 'application/json' + raise Grape::Exceptions::InvalidMessageBody.new('application/json') end end end diff --git a/lib/grape/parser/xml.rb b/lib/grape/parser/xml.rb index 20cde6e27..930c57f13 100644 --- a/lib/grape/parser/xml.rb +++ b/lib/grape/parser/xml.rb @@ -8,7 +8,7 @@ def call(object, _env) ::Grape::Xml.parse(object) rescue ::Grape::Xml::ParseError # handle XML parsing errors via the rescue handlers or provide error message - raise Grape::Exceptions::InvalidMessageBody, 'application/xml' + raise Grape::Exceptions::InvalidMessageBody.new('application/xml') end end end diff --git a/lib/grape/request.rb b/lib/grape/request.rb index 0357477d5..ed97f3194 100644 --- a/lib/grape/request.rb +++ b/lib/grape/request.rb @@ -16,7 +16,7 @@ def initialize(env, **options) def params @params ||= build_params rescue EOFError - raise Grape::Exceptions::EmptyMessageBody, content_type + raise Grape::Exceptions::EmptyMessageBody.new(content_type) end def headers diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index f8a2a1bcf..84584e0b3 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -55,7 +55,7 @@ def validate!(params) end end - raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any? + raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? end def self.convert_to_short_name(klass) diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index 03867ff1a..9ed0b6b96 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -16,7 +16,7 @@ def validate!(params) end end - raise Grape::Exceptions::ValidationArrayErrors, array_errors if array_errors.any? + raise Grape::Exceptions::ValidationArrayErrors.new(array_errors) if array_errors.any? end private From e83e81fe95d71134f2156aa6f31c22fcf954227f Mon Sep 17 00:00:00 2001 From: dblock Date: Sun, 7 Mar 2021 16:52:00 -0500 Subject: [PATCH 290/290] Preparing for release, 1.5.3. --- CHANGELOG.md | 8 +------- README.md | 3 +-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 480c27f3c..489ef8aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,7 @@ -### 1.5.3 (Next) - -#### Features - -* Your contribution here. +### 1.5.3 (2021/03/07) #### Fixes -* Your contribution here. - * [#2161](https://github.com/ruby-grape/grape/pull/2157): Handle EOFError from Rack when given an empty multipart body - [@bschmeck](https://github.com/bschmeck). * [#2162](https://github.com/ruby-grape/grape/pull/2162): Corrected a hash modification while iterating issue - [@Jack12816](https://github.com/Jack12816). * [#2164](https://github.com/ruby-grape/grape/pull/2164): Fix: `coerce_with` is now called for params with `nil` value - [@braktar](https://github.com/braktar). diff --git a/README.md b/README.md index e7f9a14b1..f74f17f74 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,8 @@ content negotiation, versioning and much more. ## Stable Release -You're reading the documentation for the next release of Grape, which should be **1.5.3**. +You're reading the documentation for the stable release of Grape, **v1.5.3**. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. -The current stable release is [1.5.2](https://github.com/ruby-grape/grape/blob/v1.5.2/README.md). ## Project Resources