orientdb-time-graph 0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fa59beca751cdc1f71752f2863b1f62b56a6e9ad4b356e660ecf0c7a218e089b
4
+ data.tar.gz: 2ba65280af40d9a79b386f0d2a52c3e2f7bf640e20c6f808672e4573d16ead45
5
+ SHA512:
6
+ metadata.gz: 363bf090b4203d4481d9bb4ce01474d764724e56f0dde7ef86af9c50ca5111325f923a2facca4c7eabfde3364fb6990fddb7650e85079bf8cebb6ac4ca1fde0e
7
+ data.tar.gz: 738430d774ee2730c863e3b9ee06afb3fcf7b6f8cb1b854d87b7118fdc233b7402288d04c728d7a9d79178ee2a11d4f0a49e3f343babb2eb8074bd13b1d56e81
@@ -0,0 +1,58 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ # Gemfile.lock
46
+ # .ruby-version
47
+ # .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
51
+
52
+ # gemfile is overridden in git
53
+ Gemfile
54
+
55
+ ## vim backupfiles
56
+ *.swp
57
+ *.swo
58
+ *~
@@ -0,0 +1,24 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ #guard :rspec, cmd: "bundle exec rspec -rdb" do
5
+ guard :rspec, cmd: "bundle exec rspec --format documentation" do
6
+ require "ostruct"
7
+
8
+ # Generic Ruby apps
9
+ rspec = OpenStruct.new
10
+ rspec.spec = ->(m) { "spec/#{m}_spec.rb" }
11
+ rspec.spec_dir = "spec"
12
+ rspec.spec_helper = "spec/spec_helper.rb"
13
+
14
+ # watch(%r{^spec/.+_spec\.rb$})
15
+ # watch(%r{^lib/(.+)\.rb$}) { |m| rspec.spec.("lib/#{m[1]}") }
16
+ # watch(rspec.spec_helper) { rspec.spec_dir }
17
+ #
18
+
19
+ watch(%r{^spec/.+_spec\.rb$})
20
+ watch(%r{^lib/(.+)\.rb$})
21
+ #watch(%r{^models/(.+)\.rb$}) { |m| "spec/models/#{m[1]}_spec.rb" }
22
+ #watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
23
+ end
24
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 topofocus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,221 @@
1
+ # Time Graph
2
+
3
+ Simple Time Graph using ActiveOrient/OrientDB.
4
+
5
+ This Graph is realized
6
+
7
+ ```ruby
8
+ Jahr -- [MONTH_OF] -- Monat --[DAY_OF]-- Tag --[TIME_OF]-- Stunde
9
+ ```
10
+ The nodes are crosslinked and any point of the grid is easily accessed.
11
+
12
+ The library provides »to_tg« additions to »Date«, »DateTime« and »String«.
13
+ Thus
14
+
15
+ ```ruby
16
+ z = "22.3.2003".to_tg
17
+ => #<TG::Tag:0x000000030d79d0 @metadata={"type"=>"d", "class"=>"tag", "version"=>4, "fieldTypes"=>"in_grid_of=g,out_grid_of=g,in_day_of=g", "cluster"=>25, "record"=>294}, @d=nil, @attributes={"value"=>22, "in_grid_of" =>["#49:304"], "out_grid_of"=>["#50:304"], "in_day_of"=>["#41:294"], "created_at"=>Mon, 12 Sep 2016 09:56:41 +0200}>
18
+ z.datum => Sat, 22 Mar 2003 (returns a Date)
19
+ z.next.datum => Sun, 23 Mar 2003
20
+ ( z + 3 ).datum => Tue, 25 Mar 2003
21
+ z.prev.datum => Fri, 21 Mar 2003
22
+ (z - 5 ).datum => Mon, 17 Mar 2003
23
+ z.move( -20 ).datum => Sun, 02 Mar 2003
24
+ z.environment( 5).datum
25
+ => ["18.5.2003", "19.5.2003", "20.5.2003", "21.5.2003", "22.5.2003", "23.5.2003", "24.5.2003", "25.5.2003", "26.5.2003", "27.5.2003", "28.5.2003"]
26
+
27
+
28
+ ```
29
+ (datum is a method of TG::Day)
30
+
31
+ *Prerequisites* :
32
+ * Ruby 2.5 (or 2.6) and OrientDB 3.0
33
+ * Install and setup ruby via RVM (rvm.io) OrientDB
34
+ * Run "bundle install" and "bundle update"
35
+ * customize config/connect.yml
36
+
37
+ **or** start a new project and require the gem in the usual manner.
38
+
39
+ Edges must be configurated with the following capitalising naming-convention
40
+ ```ruby
41
+ class E
42
+ def self.naming_convention name=nil
43
+ name.present? ? name.upcase : ref_name.upcase
44
+ end
45
+ end
46
+ ```
47
+ * Initialize the data-structure by `TG::Setup.init_database »OrientDB Database instance«` (eg. ORD)
48
+ * After restarting the application, populate the timegraph by `TG::TimeGraph.populate 2015..2030`
49
+ * In your Script activate the timegraph through `TG.connect`
50
+
51
+ To play around, start the console by
52
+ ```
53
+ cd bin
54
+ ./console t # test-modus
55
+ ```
56
+ call `TG::Init_database` and restart the console
57
+
58
+ The following database classes are build
59
+ ```ruby
60
+ - E # ruby-class
61
+ - - month_of TG::MONTH_OF
62
+ - - day_of TG::DAY_OF
63
+ - - time_of TG::TIME_OF
64
+ - - grid_of TG::GRID_OF
65
+ - V
66
+ - - time_base TG::TimeBase
67
+ - - - jahr TG::Jahr
68
+ - - - monat TG::Monat
69
+ - - - stunde TG::Stunde
70
+ - - - tag TG::Tag
71
+ ```
72
+
73
+ The graph is populated by calling
74
+
75
+ ```ruby
76
+ TG::TimeGraph.populate( a single year or a range ) # default: 1900 .. 2050
77
+ ```
78
+ (restart the console after this and check if all classes are assigned)
79
+
80
+ If only one year is specified, a Monat--Tag--Stunde-Grid is build, otherwise a Jahr--Monat--Tag one.
81
+ You can check the Status by calling
82
+
83
+
84
+ ```ruby
85
+ TG::TimeGraph.populate 2000..2003
86
+ TG.info
87
+ -------------- TIME GRAPH ------------------
88
+ Allocated Years :
89
+ 2000; 2001; 2002; 2003
90
+
91
+ ```
92
+ In the Model-directory, customized methods simplify the usage of the graph.
93
+
94
+ Some Examples:
95
+ Assuming, you build a standard day-based grid
96
+
97
+ ```ruby
98
+
99
+ include TG # we can omit the TG prefix
100
+
101
+ Jahr[2000] # --> returns a single object
102
+ => #<TG::Jahr:0x00000004ced160 @metadata={"type"=>"d", "class"=>"jahr", "version"=>13, "fieldTypes"=>"out_month_of=g", "cluster"=>34, "record"=>101}, @d=nil, @attributes={"value"=>2000, "out_month_of"=>["#53:1209", "#54:1209", "#55:1209", "#56:1209", "#53:1210", "#54:1210", "#55:1210", "#56:1210", "#53:1211", "#54:1211", "#55:1211", "#56:1211"], "created_at"=>Fri, 09 Sep 2016 10:14:30 +0200}>
103
+
104
+
105
+ Jahr[2000 .. 2005].value # returns an array
106
+ => [2003, 2000, 2004, 2001, 2005, 2002]
107
+
108
+ Jahr[2000 .. 2005].monat(5..7).value # returns the result of the month-attribute (or method)
109
+ => [[5, 6, 7], [5, 6, 7], [5, 6, 7], [5, 6, 7], [5, 6, 7], [5, 6, 7]]
110
+
111
+ Jahr[2000].monat(4, 7).tag(4, 15,24 ).datum # adresses methods or attributes of the specified day's
112
+ => [["4.4.2000", "15.4.2000", "24.4.2000"], ["4.7.2000", "15.7.2000", "24.7.2000"]]
113
+ ``
114
+
115
+ To filter datasets in that way, anything represented is queried from the database. In contrast to
116
+ a pure ruby implementation, this works for small and large grid's.
117
+
118
+ Obviously, you can do neat ruby-array playings, which are limited to the usual sizes.
119
+ For example. As »TAG[31]« returns an array, the elements can be treated with ruby flavour:
120
+
121
+ ```ruby
122
+
123
+ Tag[31][2..4].datum # display three months with 31 days
124
+ => ["31.10.1901", "31.1.1902", "31.5.1902"]
125
+
126
+ ```
127
+ First all Tag-Objects with the Value 31 are queried. This gives »Jan, Mar, May ..«. Then one can inspect the array, in this case by slicing a range.
128
+
129
+ Not surprisingly, the first occurence of the day is not the earliest date in the grid. Its just the first one,
130
+ fetched from the database.
131
+
132
+ ``` ruby
133
+ Tag[1][1].datum
134
+ => "1.5.1900" # Tag[1][0] correctly fetches "1.1.1900"
135
+ Tag[1].last.datum
136
+ => "1.11.2050"
137
+ ## however,
138
+ Jahr[2050].monat(12).tag(1) # exists:
139
+ => ["1.12.2050"]
140
+
141
+ TG::Jahr[2000..2002].monat(4,12).tag(1..4).datum.flatten
142
+ => [Sat, 01 Apr 2000, Sun, 02 Apr 2000, Mon, 03 Apr 2000, Tue, 04 Apr 2000, Fri, 01 Dec 2000, Sat, 02 Dec 2000, Sun, 03 Dec 2000, Mon, 04 Dec 2000,
143
+ Sun, 01 Apr 2001, Mon, 02 Apr 2001, Tue, 03 Apr 2001, Wed, 04 Apr 2001, Sat, 01 Dec 2001, Sun, 02 Dec 2001, Mon, 03 Dec 2001, Tue, 04 Dec 2001,
144
+ Mon, 01 Apr 2002, Tue, 02 Apr 2002, Wed, 03 Apr 2002, Thu, 04 Apr 2002, Sun, 01 Dec 2002, Mon, 02 Dec 2002, Tue, 03 Dec 2002, Wed, 04 Dec 2002]
145
+ ```
146
+
147
+ ## Horizontal Connections
148
+
149
+ Besides the hierarchically TimeGraph »Jahr <-->Monat <--> Tag <--> Stunde« the Vertices are connected
150
+ horizontally via »grid_to«-Edges. This enables an easy access to the neighbours.
151
+
152
+ On the TG::TimeBase-Level a method »environment« is implemented, that gathers the adjacent vertices
153
+ via traverse.
154
+
155
+ ``` ruby
156
+ start = "7.4.2000".to_tg
157
+ start.environment(3).datum
158
+ => ["4.4.2000", "5.4.2000", "6.4.2000", "7.4.2000", "8.4.2000", "9.4.2000", "10.4.2000"]
159
+
160
+ 2.3.1 :003 > start.environment(3,4).datum
161
+ => ["4.4.2000", "5.4.2000", "6.4.2000", "7.4.2000", "8.4.2000", "9.4.2000", "10.4.2000", "11.4.2000"]
162
+
163
+ start.environment(0,3).datum
164
+ => ["7.4.2000", "8.4.2000", "9.4.2000", "10.4.2000"]
165
+
166
+
167
+ ```
168
+
169
+ ## Assigning Events
170
+
171
+ To assign something to the TimeGrid its sufficiant to create an edge-class and connect this »something»,
172
+ which is represented as Vertex, to the grid. The Diary example below describes how to do it.
173
+
174
+ However, if a csv-file with a »date« column is present, it's easier to assign it directly
175
+ to the grid:
176
+
177
+ ``` ruby
178
+ # csv record
179
+ Ticker,Date/Time,Open,High,Low,Close,Volume,Open Interest,
180
+ ^GSPTSE,09.09.2016,14717.23,14717.23,14502.90,14540.00,202109040,0
181
+ ```
182
+ assuming the record is read as string, then assigning is straightforward:
183
+ ``` ruby
184
+ ticker, date, open, high, low, close, volume, oi = record.split(',')
185
+ date.to_tg.assign vertex: Ticker.new( high: high, ..), via: OHLC_TO, attributes:{ symbol: ticker }
186
+ ```
187
+ The updated TimeBase-Object is returned.
188
+
189
+ »OHLC_TO« is the edge-class and »Ticker« represents a vertex-class
190
+ ## Diary
191
+
192
+ lets create a simple diary
193
+
194
+ ```ruby
195
+ include TG
196
+ TimeGraph.populate 2016
197
+ V.create_class :termin
198
+ => Termin
199
+ E.create_class :date_of
200
+ => DATE_OF
201
+ DATE_OF.uniq_index # put contrains to the edge-class, accept only one entry per item
202
+
203
+ Monat[8].tag(9).stunde(12).assign vertex: Termin.create( short: 'Mittagessen',
204
+ long: 'Schweinshaxen essen mit Lieschen Müller',
205
+ location: 'Hofbauhaus, München' ),
206
+ via: DATE_OF
207
+ => #<DATE_OF:0x0000000334e038 (..) @attributes={"out"=>"#21:57", "in"=>"#41:0", (..)}>
208
+ # create some regular events using Edge.create
209
+ # attach breakfirst at 9 o clock from the 10th to the 21st Day in the current month
210
+ DATE_OF.create from: Monat[8].tag( 10 .. 21 ).stunde( 9 ), to: Termin.create( :short => 'Frühstück' )
211
+ => #<DATE_OF:0x000000028d5688 @metadata={(..) "cluster"=>45, "record"=>8},
212
+ @attributes={"out"=>"#22:188", "in"=>"#42:0",(..)}>
213
+
214
+ t = Termin.where short: 'Frühstück'
215
+ t.in_date_of.out.first.datum
216
+ => ["10.8.2016 9:00", "11.8.2016 9:00", "12.8.2016 9:00", "13.8.2016 9:00", "14.8.2016 9:00", "15.8.2016 9:00", "16.8.2016 9:00", "17.8.2016 9:00", "18.8.2016 9:00", "19.8.2016 9:00", "20.8.2016 9:00", "21.8.2016 9:00"]
217
+
218
+
219
+
220
+ ```
221
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+ ## loads the active-orient environment
3
+ ## and starts an interactive shell
4
+ ## Parameter: production (p)
5
+ ## development (d) [default]
6
+ ## test (t)
7
+ require 'bundler/setup'
8
+ require_relative '../lib/orientdb_time_graph'
9
+ require_relative '../lib/setup'
10
+ require 'logger'
11
+ require 'pastel'
12
+ LogLevel = Logger::INFO
13
+
14
+ p = Pastel.new
15
+ c = p.bright_white.bold.on_yellow.detach
16
+ g = p.green.on_black.detach
17
+
18
+ #require File.expand_path(File.dirname(__FILE__) + "/../config/boot")
19
+
20
+ specified_environment = ARGV[0] || 'D'
21
+ env = case specified_environment[-1].upcase
22
+ when 'P'
23
+ :production
24
+ when 'D'
25
+ :development
26
+ else
27
+ :test
28
+ end
29
+ module TG; end
30
+ TG.setup env
31
+
32
+ ActiveOrient::Init.define_namespace namespace: :object
33
+ ActiveOrient::Model.keep_models_without_file = true
34
+ ActiveOrient::Model.keep_models_without_file = true
35
+ ActiveOrient::OrientDB.new preallocate: true
36
+
37
+
38
+ #TG.check_and_initialize ORD
39
+
40
+ #require 'orientdb' if RUBY_PLATFORM == 'java'
41
+ require 'yaml'
42
+
43
+ class Array
44
+ # Method missing enables fancy stuff like
45
+ # Jahr[2000 .. 2005].monat(5..7).value
46
+ #
47
+ # its included only in the console, for inspection purposes
48
+
49
+ def method_missing(method, *key)
50
+ unless method == :to_hash || method == :to_str #|| method == :to_int
51
+ return self.map{|x| x.public_send(method, *key)}
52
+ end
53
+
54
+ end
55
+ end # Array
56
+
57
+
58
+ puts '-'* 45
59
+ ns= case ActiveOrient::Model.namespace
60
+ when Object
61
+ "No Prefix, just ClassName#CamelCase"
62
+ else
63
+ ActiveOrient::Model.namespace.to_s + "{ClassName.camelcase}"
64
+ end
65
+ puts "Namespace for model-classes : #{ns}"
66
+ puts "Present Classes (Hierarchy) "
67
+
68
+ puts V.db.class_hierarchy.to_yaml
69
+ puts ActiveOrient::show_classes
70
+
71
+ include OrientDB
72
+
73
+ require 'irb'
74
+ ARGV.clear
75
+ IRB.start(__FILE__)
@@ -0,0 +1,119 @@
1
+ # ######################## Outdated ##################
2
+ #
3
+ # do not use anymore
4
+ #
5
+ # instead
6
+ # require lib/setup
7
+ # and include
8
+ # TG.setup environment in the startup-script
9
+ # with environment = :test, :development, :production
10
+ # followed by
11
+ # TG.connect
12
+ #
13
+ #
14
+ #
15
+ #
16
+ #
17
+ ### Parameter: ARGV (Array, argument from the command line)
18
+ ### t)test
19
+ ### d)velopement
20
+ ### p)roduction
21
+ ###
22
+ ###
23
+
24
+ require 'bundler/setup'
25
+ require 'yaml'
26
+ require 'active-orient'
27
+ if RUBY_VERSION == 'java'
28
+ require 'orientdb'
29
+ end
30
+ project_root = File.expand_path('../..', __FILE__)
31
+ require "#{project_root}/lib/orientdb_time_graph.rb"
32
+ begin
33
+ connect_file = File.expand_path('../../config/connect.yml', __FILE__)
34
+ config_file = File.expand_path('../../config/config.yml', __FILE__)
35
+ connectyml = YAML.load_file( connect_file )[:orientdb][:admin] if connect_file.present?
36
+ configyml = YAML.load_file( config_file )[:active_orient] if config_file.present?
37
+ rescue Errno::ENOENT => e
38
+ ActiveOrient::Base.logger = Logger.new('/dev/stdout')
39
+ ActiveOrient::OrientDB.logger.error{ "config/connectyml not present" }
40
+ ActiveOrient::OrientDB.logger.error{ "Using defaults to connect database-server" }
41
+ end
42
+
43
+ e= ARGV.present? ? ARGV.last.downcase : 'development'
44
+ env = if e =~ /^p/
45
+ 'production'
46
+ elsif e =~ /^t/
47
+ 'test'
48
+ else
49
+ 'development'
50
+ end
51
+ puts "Using #{env}-environment"
52
+
53
+
54
+ # lib/init.rb
55
+
56
+ ActiveOrient::Model.model_dir = "#{project_root}/#{ configyml.present? ? configyml[:model_dir] : "model" }"
57
+ puts "BOOT--> Project-Root: #{project_root}"
58
+ puts "BOOT--> Model-dir: #{ActiveOrient::Model.model_dir}"
59
+
60
+ databaseyml = YAML.load_file( connect_file )[:orientdb][:database]
61
+ log_file = if config_file.present?
62
+ dev = YAML.load_file( connect_file )[:orientdb][:logger]
63
+ if dev.blank? || dev== 'stdout'
64
+ '/dev/stdout'
65
+ else
66
+ project_root+'/log/'+env+'.log'
67
+ end
68
+ end
69
+
70
+
71
+ logger = Logger.new log_file
72
+ logger.level = case env
73
+ when 'production'
74
+ Logger::ERROR
75
+ when 'development'
76
+ Logger::WARN
77
+ else
78
+ Logger::INFO
79
+ end
80
+ logger.formatter = proc do |severity, datetime, progname, msg|
81
+ "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{progname}:..:#{msg}\n"
82
+ end
83
+ ActiveOrient::Model.logger = logger
84
+ ActiveOrient::OrientDB.logger = logger
85
+ if connectyml.present? and connectyml[:user].present? and connectyml[:pass].present?
86
+ ActiveOrient.default_server= { user: connectyml[:user], password: connectyml[:pass] ,
87
+ server: '172.28.50.25', port: 2480 }
88
+ ActiveOrient.database = databaseyml[env.to_sym]
89
+ ActiveOrient::Init.define_namespace namespace: :object
90
+ ## Include customized NamingConvention for Edges
91
+ ORD = ActiveOrient::OrientDB.new preallocate: true
92
+ ORD.create_class 'E'
93
+ ORD.create_class 'V'
94
+ class E < ActiveOrient::Model
95
+ def self.naming_convention name=nil
96
+ name.present? ? name.upcase : ref_name.upcase
97
+ end
98
+ end
99
+
100
+ TG.connect
101
+
102
+ # load any unallocated database class, even it there is no model-file present
103
+ ActiveOrient::Model.keep_models_without_file = true
104
+
105
+
106
+ if RUBY_PLATFORM == 'java'
107
+ DB = ActiveOrient::API.new preallocate: false
108
+ else
109
+ DB = ORD
110
+ end
111
+
112
+ else
113
+ ActiveOrient::OrientDB.logger = Logger.new('/dev/stdout')
114
+ ActiveOrient::OrientDB.logger.error{ "config/connectyml is misconfigurated" }
115
+ ActiveOrient::OrientDB.logger.error{ "Database Server is NOT available"}
116
+ end
117
+
118
+
119
+