was 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +98 -12
- data/lib/was/score.rb +69 -7
- data/lib/was/tree.rb +31 -0
- data/lib/was/version.rb +1 -1
- data/lib/was.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 974fa97c47b4fe794512983c75fa065b9bdd40370e5987b1e4523d01791bdd2a
|
4
|
+
data.tar.gz: 9f6e783dd2fbad215e8c2b16c820a1b8642b843efba424ed2d35e5aa1e7e2b92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43d1c61377ad1da1477d5edf911fd55b22acea765ffcf10e53c3cdc2c181e7968bd3bc02223721a82146c57b5186f79d25b75557026cedecb30dfc218a56e801
|
7
|
+
data.tar.gz: 6c09a8018b3d7fb5634985e3144d75adc942a21b3b6b459057192b1f0918bfee840b54a8897dc9ad25983623cbdf858b40c9e72f84ab073d3a891592998e5d41
|
data/README.md
CHANGED
@@ -14,8 +14,14 @@ Scenario:
|
|
14
14
|
* 'C' is 50%
|
15
15
|
* 'D' is 0%
|
16
16
|
* The practical is a simple mark out of 10.
|
17
|
-
* 4 out of 10 is 40%
|
18
|
-
* The person is given a final score out of 1000.
|
17
|
+
* 4 out of 10 is 40%
|
18
|
+
* The person is given a final score out of 1000.
|
19
|
+
|
20
|
+
### Install the RubyGem
|
21
|
+
|
22
|
+
```bash
|
23
|
+
gem install was
|
24
|
+
```
|
19
25
|
|
20
26
|
### Define score classes
|
21
27
|
|
@@ -25,21 +31,30 @@ require "was"
|
|
25
31
|
class ReportScore < WAS::Score
|
26
32
|
maximum_score 1000
|
27
33
|
|
28
|
-
with :exam,
|
29
|
-
with :practical class_name: "PracticalScore", weight: 0.25
|
34
|
+
with :exam, class_name: "ExamScore", weight: 0.75
|
35
|
+
with :practical, class_name: "PracticalScore", weight: 0.25
|
30
36
|
end
|
31
37
|
|
32
38
|
class ExamScore < WAS::Score
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
context :grade_a, score: 1 do |input|
|
40
|
+
input == "A"
|
41
|
+
end
|
42
|
+
|
43
|
+
context :grade_b, score: 0.75 do |input|
|
44
|
+
input == "B"
|
45
|
+
end
|
46
|
+
|
47
|
+
context :grade_c, score: 0.5 do |input|
|
48
|
+
input == "C"
|
49
|
+
end
|
50
|
+
|
51
|
+
context :flunk do
|
37
52
|
0
|
38
53
|
end
|
39
54
|
end
|
40
55
|
|
41
56
|
class PracticalScore < WAS::Score
|
42
|
-
|
57
|
+
context :score do |input|
|
43
58
|
input / 10.0
|
44
59
|
end
|
45
60
|
end
|
@@ -68,8 +83,8 @@ Omitting the `maximum_score` will return a composed percentage represented as a
|
|
68
83
|
```ruby
|
69
84
|
# report_score.rb
|
70
85
|
class ReportScore < WAS::Score
|
71
|
-
with :exam,
|
72
|
-
with :practical class_name: "PracticalScore", weight: 0.25
|
86
|
+
with :exam, class_name: "ExamScore", weight: 0.75
|
87
|
+
with :practical, class_name: "PracticalScore", weight: 0.25
|
73
88
|
end
|
74
89
|
```
|
75
90
|
|
@@ -82,9 +97,80 @@ ReportScore.new({
|
|
82
97
|
#> 0.875
|
83
98
|
```
|
84
99
|
|
100
|
+
### Working with a score tree
|
101
|
+
|
102
|
+
For more complex scenarios, we might need to know more about how the score was composed.
|
103
|
+
|
104
|
+
Using the example `ReportScore`, `ExamScore`, and `PracticalScore` classes as
|
105
|
+
an example, we can generate a tree of scores:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
tree = ReportScore.new({
|
109
|
+
exam: "A",
|
110
|
+
practical: 5
|
111
|
+
}).calculate(:tree)
|
112
|
+
|
113
|
+
#> tree
|
114
|
+
{
|
115
|
+
score: 875.0,
|
116
|
+
max: 1000,
|
117
|
+
deduction: -125.0,
|
118
|
+
with: {
|
119
|
+
exam: {
|
120
|
+
score: 750.0,
|
121
|
+
max: 750.0,
|
122
|
+
deduction: 0.0,
|
123
|
+
weight: 0.75
|
124
|
+
},
|
125
|
+
practical: {
|
126
|
+
score: 125.0,
|
127
|
+
max: 250.0,
|
128
|
+
deduction: -125.0,
|
129
|
+
weight: 0.25
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
```
|
134
|
+
|
135
|
+
This result is a `WAS::Tree` object. Basically a `Hash` with some extra
|
136
|
+
added to it.
|
137
|
+
|
138
|
+
#### Ordering by attribute
|
139
|
+
|
140
|
+
For each of the key attributes `:score`, `:max`, `:weight`, and
|
141
|
+
`:deduction`, we can order our tree interally by that key:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
#> tree.order(:deduction)
|
145
|
+
{
|
146
|
+
score: 875.0,
|
147
|
+
max: 1000,
|
148
|
+
deduction: -125.0
|
149
|
+
with: {
|
150
|
+
practical: {
|
151
|
+
score: 125.0,
|
152
|
+
max: 250.0,
|
153
|
+
deduction: -125.0,
|
154
|
+
weight: 0.25
|
155
|
+
},
|
156
|
+
exam: {
|
157
|
+
score: 750.0,
|
158
|
+
max: 750.0,
|
159
|
+
deduction: 0.0,
|
160
|
+
weight: 0.75
|
161
|
+
}
|
162
|
+
}
|
163
|
+
}
|
164
|
+
```
|
165
|
+
|
166
|
+
For `:deduction`, results adjacent to each other are ordered by the
|
167
|
+
_most negative_. For all other options, the values are ordered largest
|
168
|
+
value first.
|
169
|
+
|
85
170
|
### View all weights
|
86
171
|
|
87
|
-
If you want to see all of the weights that are used to compose the score,
|
172
|
+
If you want to see all of the weights that are used to compose the score,
|
173
|
+
there is a convenience method `.weights`:
|
88
174
|
|
89
175
|
```ruby
|
90
176
|
ReportScore.weights
|
data/lib/was/score.rb
CHANGED
@@ -40,20 +40,62 @@ module WAS
|
|
40
40
|
@input = input
|
41
41
|
end
|
42
42
|
|
43
|
-
def calculate
|
44
|
-
calculation
|
43
|
+
def calculate(option = nil)
|
44
|
+
return calculation if option != :tree
|
45
|
+
|
46
|
+
calc = calculation(:tree)
|
47
|
+
tree = if calc.is_a?(Hash)
|
48
|
+
calc.merge(additional_score_attributes(calc[:score]))
|
49
|
+
else
|
50
|
+
{}.tap do |t|
|
51
|
+
t.merge!(score: calc)
|
52
|
+
t.merge!(additional_score_attributes(calc))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
transform_scores_relative_to_max_score(tree)
|
45
57
|
end
|
46
58
|
|
47
|
-
def calculation
|
59
|
+
def calculation(option = nil)
|
48
60
|
if contexts?
|
49
61
|
context_score_calculation
|
50
62
|
else
|
51
|
-
nested_score_calcuation
|
63
|
+
nested_score_calcuation(option)
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
67
|
private
|
56
68
|
|
69
|
+
def additional_score_attributes(score_value)
|
70
|
+
{
|
71
|
+
max: self.class.max_score,
|
72
|
+
deduction: (score_value - self.class.max_score).round(8)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def transform_scores_relative_to_max_score(tree, max_score = nil)
|
77
|
+
max_score = max_score || self.class.max_score
|
78
|
+
return tree if self.class.max_score == 1
|
79
|
+
|
80
|
+
tree.each do |key, value|
|
81
|
+
next if key != :with
|
82
|
+
|
83
|
+
value.each do |scorer, branch|
|
84
|
+
adjust_values_relative_to_max_score(branch, max_score)
|
85
|
+
end
|
86
|
+
|
87
|
+
value.transform_values! do |nested_tree|
|
88
|
+
transform_scores_relative_to_max_score(nested_tree, nested_tree[:max])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def adjust_values_relative_to_max_score(branch, max_score)
|
94
|
+
branch[:max] = branch[:max] * max_score * branch[:weight]
|
95
|
+
branch[:score] = branch[:score] * branch[:max]
|
96
|
+
branch[:deduction] = branch[:score] - branch[:max]
|
97
|
+
end
|
98
|
+
|
57
99
|
def contexts?
|
58
100
|
!!self.class.instance_variable_get("@contexts")
|
59
101
|
end
|
@@ -61,16 +103,36 @@ module WAS
|
|
61
103
|
def context_score_calculation
|
62
104
|
self.class.instance_variable_get("@contexts").each do |context|
|
63
105
|
output = context[:code].call(input)
|
64
|
-
next
|
106
|
+
next if !output
|
65
107
|
return context[:score] || output
|
66
108
|
end
|
67
109
|
end
|
68
110
|
|
69
|
-
def nested_score_calcuation
|
70
|
-
|
111
|
+
def nested_score_calcuation(option)
|
112
|
+
if option == :tree
|
113
|
+
WAS::Tree.new.tap do |t|
|
114
|
+
t[:score] = sum
|
115
|
+
t[:with] = with_attribute
|
116
|
+
end
|
117
|
+
else
|
118
|
+
sum
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def sum
|
123
|
+
@sum ||= self.class.scorers.sum do |name, scorer|
|
71
124
|
score = Object.const_get(scorer[:class_name]).new(input[name.to_sym]).calculate
|
72
125
|
score * scorer[:weight]
|
73
126
|
end * self.class.max_score
|
74
127
|
end
|
128
|
+
|
129
|
+
def with_attribute
|
130
|
+
{}.tap do |with|
|
131
|
+
self.class.scorers.each do |name, scorer|
|
132
|
+
with[name] = Object.const_get(scorer[:class_name]).new(input[name.to_sym]).calculate(:tree)
|
133
|
+
with[name][:weight] = scorer[:weight]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
75
137
|
end
|
76
138
|
end
|
data/lib/was/tree.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module WAS
|
2
|
+
class Tree < Hash
|
3
|
+
def order(order_key = :deduction)
|
4
|
+
self.dup.tap do |tree|
|
5
|
+
return tree if tree[:with].nil?
|
6
|
+
|
7
|
+
sort_with_by!(order_key, tree)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def sort_with_by!(order_key, tree)
|
14
|
+
return tree if tree[:with].nil?
|
15
|
+
|
16
|
+
tree[:with].each do |_, subtree|
|
17
|
+
sort_with_by!(order_key, subtree)
|
18
|
+
end
|
19
|
+
|
20
|
+
array = tree[:with].sort_by do |_, subtree|
|
21
|
+
subtree[order_key]
|
22
|
+
end
|
23
|
+
|
24
|
+
tree[:with] = if order_key == :deduction
|
25
|
+
array.to_h
|
26
|
+
else
|
27
|
+
array.reverse.to_h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/was/version.rb
CHANGED
data/lib/was.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: was
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Connell
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A simple gem/dsl for generating Weighted Average Score calculations.
|
14
14
|
email:
|
@@ -23,6 +23,7 @@ files:
|
|
23
23
|
- Rakefile
|
24
24
|
- lib/was.rb
|
25
25
|
- lib/was/score.rb
|
26
|
+
- lib/was/tree.rb
|
26
27
|
- lib/was/version.rb
|
27
28
|
- sig/was.rbs
|
28
29
|
- was.gemspec
|