Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
---|---|---|---|---|
lib/jldrill/model/Quiz/Statistics.rb | 316 | 225 | 93.04%
|
90.22%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 require 'jldrill/model/Item' |
2 require 'jldrill/model/ItemStatus' |
3 require 'jldrill/model/Quiz/Timer' |
4 require 'jldrill/model/Quiz/LevelStats' |
5 require 'jldrill/model/Quiz/Counter' |
6 |
7 module JLDrill |
8 |
9 # Statistics for a quiz session |
10 class Statistics |
11 |
12 attr_reader :estimate, :lastTen, :confidence, :levels, |
13 :timesInTargetZone, :learned, :reviewed |
14 |
15 attr_writer :learned, :reviewed |
16 |
17 MINIMUM_CONFIDENCE = 0.009 |
18 SECONDS_PER_DAY = 60 * 60 * 24 |
19 |
20 def initialize(quiz) |
21 @quiz = quiz |
22 @estimate = 0 |
23 @correct = 0 |
24 @incorrect = 0 |
25 @lastTen = [] |
26 @inTargetZone = false |
27 @timesInTargetZone = 0 |
28 @levels = [] |
29 1.upto(8) do |
30 @levels.push(LevelStats.new) |
31 end |
32 @learned = 0 |
33 @reviewed = 0 |
34 @reviewRateSum = 0 |
35 @reviewTimer = Timer.new |
36 @learnTimer = Timer.new |
37 @currentTimer = nil |
38 resetConfidence |
39 end |
40 |
41 def record(bool) |
42 @lastTen.push(bool) |
43 while @lastTen.size > 10 |
44 @lastTen.delete_at(0) |
45 end |
46 if inTargetZone? |
47 @timesInTargetZone += 1 |
48 else |
49 @timesInTargetZone = 0 |
50 end |
51 end |
52 |
53 def recentAccuracy |
54 retVal = 0 |
55 if @lastTen.size > 0 |
56 0.upto(@lastTen.size) do |i| |
57 if @lastTen[i] |
58 retVal += 1 |
59 end |
60 end |
61 retVal = (retVal * 100 / @lastTen.size).to_i |
62 end |
63 retVal |
64 end |
65 |
66 def inTargetZone? |
67 # Don't start the countdown until we have reviewed |
68 # at least 10 item. |
69 if @reviewed <= 10 |
70 return false |
71 end |
72 if !@inTargetZone |
73 if recentAccuracy >= 90 |
74 @inTargetZone = true |
75 end |
76 else |
77 if (recentAccuracy < 90) && (@confidence < 90) |
78 @inTargetZone = false |
79 end |
80 end |
81 return @inTargetZone |
82 end |
83 |
84 # Get the appropriate LevelStat object for this item |
85 def getLevel(item) |
86 return @levels[Counter.getLevel(item)] |
87 end |
88 |
89 def recordReviewRate(item) |
90 @reviewRateSum += item.schedule.reviewRate |
91 end |
92 |
93 def reviewBin |
94 return @quiz.contents.bins[4] |
95 end |
96 |
97 def size |
98 return reviewBin.length |
99 end |
100 |
101 def averageReviewRate |
102 retVal = 1.0 |
103 if @reviewed != 0 |
104 retVal = ((@reviewRateSum * 10).round / @reviewed).to_f / 10 |
105 end |
106 return retVal |
107 end |
108 |
109 def currentReviewRate |
110 retVal = 1.0 |
111 if size > 0 |
112 rate = reviewBin[0].schedule.reviewRate |
113 retVal = ((rate * 100).round).to_f / 100 |
114 end |
115 retVal |
116 end |
117 |
118 def statsTable |
119 dCounter = DurationCounter.new |
120 |
121 reviewBin.each do |item| |
122 dCounter.count(item) |
123 end |
124 return dCounter |
125 end |
126 |
127 def correct(item) |
128 # currently only level 4 items are reviewed |
129 if item.bin != 4 |
130 return |
131 end |
132 @correct += 1 |
133 @reviewed += 1 |
134 recordReviewRate(item) |
135 level = getLevel(item) |
136 if !level.nil? |
137 level.correct |
138 end |
139 record(true) |
140 reEstimate |
141 calculateConfidence(true) |
142 end |
143 |
144 def incorrect(item) |
145 # currently only level 4 items are reviewed |
146 if item.bin != 4 |
147 return |
148 end |
149 @incorrect += 1 |
150 @reviewed += 1 |
151 recordReviewRate(item) |
152 level = getLevel(item) |
153 if !level.nil? |
154 level.incorrect |
155 end |
156 record(false) |
157 reEstimate |
158 calculateConfidence(false) |
159 end |
160 |
161 def correct=(value) |
162 @correct = value |
163 reEstimate |
164 end |
165 |
166 def incorrect=(value) |
167 @incorrect = value |
168 reEstimate |
169 end |
170 |
171 # Returns the actual % accuracy of the quiz in an integer |
172 def accuracy |
173 retVal = 0 |
174 if @incorrect == 0 |
175 if @correct != 0 |
176 retVal = 100 |
177 end |
178 else |
179 retVal = ((@correct * 100) / total).to_i |
180 end |
181 retVal |
182 end |
183 |
184 def total |
185 @correct + @incorrect |
186 end |
187 |
188 # Generates an estimate of the % accuracy in an integer |
189 def reEstimate |
190 hop = ((recentAccuracy - @estimate) * 0.3).to_i |
191 retVal = @estimate + hop |
192 if (retVal > 100) then retVal = 100 end |
193 if (retVal < 0) then retVal = 0 end |
194 @estimate = retVal |
195 end |
196 |
197 # Calculates a Bayesian estimate (I think!) of the confidence that |
198 # the items seen to this point were known to an accuracy of at |
199 # least 90%. |
200 # |
201 # How this is calculated: |
202 # According to Bayes law, P(H|E) = P(E|H) * P(H) / P(E) |
203 # Where: |
204 # P(H|E) is the probability that the hypothesis is correct |
205 # given the evidence. |
206 # P(E|H) is the probability that the evididence would be |
207 # shown if the hypothesis were correct. |
208 # P(H) is the probability that the hypothesis is correct |
209 # given any evidence. |
210 # P(E) is the probability that the evidence would be seen |
211 # in all cases. |
212 # |
213 # This value is calculated incrementally in the following fashion: |
214 # P[n] = P(E|H) * P[n-1] / P(E) |
215 # i.e., the nth value of P is dependent on the n-1th value of P |
216 # |
217 # In our case, I will assume that the distribution of the |
218 # probabilities is uniform (probably wrong!) and say that |
219 # if we got the question right |
220 # P(E|H) = 0.95 |
221 # that is, our probability of getting it right is |
222 # somewhere between 90 and 100%. I assume uniform |
223 # distribution, therefore the average is in the middle. |
224 # or if we got the question wrong |
225 # P(E|H) = 0.05 |
226 # obviously, the probability of getting it wrong given |
227 # the hypothesis is 1 minus the probability of getting |
228 # it right. |
229 # P(E) = sum(probabilities for all the hypotheses) |
230 # = P(chance < 90) + P(chance is 90+%) |
231 # P(chance < 90) = 0.45(1 - P[n-1]) |
232 # that is, it's equal to 1/2 the range between 0 and 90 |
233 # times the probability that hypothesis is wrong. |
234 # P(chance > 90) = 0.95P[n-1] |
235 # that is, it's equal to 1/2 the range between 90 and 100 |
236 # times the probability that the hypothesis is right. |
237 # So... |
238 # P(E) = 0.45(1 - P[n-1]) + 0.95P[n-1] |
239 # |
240 # Giving a final answer of |
241 # P[n] = P(E|H) * P[n-1] / (0.45(1 - P[n-1]) + 0.95P[n-1]) |
242 # |
243 # I fully expect this is completely wrong due to my lack of |
244 # ability in math and statistics. But on the other hand, I won't |
245 # care much as long as appears from the user's point of view to |
246 # be working. |
247 def calculateConfidence(wasCorrect) |
248 if wasCorrect |
249 pEH = 0.95 |
250 else |
251 pEH = 0.05 |
252 end |
253 pE = (0.45 * (1.0 - @confidence)) + (0.95 * @confidence) |
254 @confidence = (pEH * @confidence) / pE |
255 if @confidence < MINIMUM_CONFIDENCE |
256 @confidence = MINIMUM_CONFIDENCE |
257 end |
258 end |
259 |
260 def resetConfidence |
261 @confidence = MINIMUM_CONFIDENCE |
262 end |
263 |
264 def startTimer(isReview) |
265 if !@currentTimer.nil? |
266 @currentTimer.stop |
267 end |
268 if isReview |
269 @currentTimer = @reviewTimer |
270 else |
271 @currentTimer = @learnTimer |
272 end |
273 @currentTimer.start |
274 end |
275 |
276 def learnTime |
277 @learnTimer.total |
278 end |
279 |
280 def reviewTime |
281 @reviewTimer.total |
282 end |
283 |
284 def roundToOneDecimal(value) |
285 value = value * 10.0 |
286 value = value.round |
287 value = value.to_f / 10.0 |
288 value |
289 end |
290 |
291 def learnPace |
292 if @learned > 0 |
293 roundToOneDecimal(learnTime / @learned) |
294 else |
295 0.0 |
296 end |
297 end |
298 |
299 def reviewPace |
300 if @reviewed > 0 |
301 roundToOneDecimal(reviewTime / @reviewed) |
302 else |
303 0.0 |
304 end |
305 end |
306 |
307 def learnTimePercent |
308 total = learnPace + reviewPace |
309 if total > 0 |
310 ((learnPace * 100) / total).to_i |
311 else |
312 0 |
313 end |
314 end |
315 end |
316 end |
Generated on Mon May 23 16:17:46 +0900 2011 with rcov 0.9.8