|
@@ -188,6 +188,28 @@ Number.prototype.toCurrency = function() {
|
188
|
188
|
return this.toFixed(2).replace(/([0-9])(?=([0-9]{3})+\.)/g, '$1,');
|
189
|
189
|
};
|
190
|
190
|
|
|
191
|
+/**
|
|
192
|
+ * Computes the arithmatic mean, variance and deviation for the given array.
|
|
193
|
+ *
|
|
194
|
+ * @param a Array of numbers to be averaged
|
|
195
|
+ * @return A map containing the mean, variance and deviation
|
|
196
|
+ */
|
|
197
|
+function getAverage(a){
|
|
198
|
+ var r = {mean: 0, variance: 0, deviation: 0};
|
|
199
|
+ var length = a.length;
|
|
200
|
+
|
|
201
|
+ // Sum the array
|
|
202
|
+ for (var sum = 0, i = length; i--; sum += a[i]);
|
|
203
|
+
|
|
204
|
+ var mean = r.mean = sum / length
|
|
205
|
+
|
|
206
|
+ // Sum the squares of the differences from the mean
|
|
207
|
+ for (var i = length, sum = 0; i--; sum += Math.pow(a[i] - mean, 2));
|
|
208
|
+
|
|
209
|
+ r.deviation = Math.sqrt(r.variance = sum / length)
|
|
210
|
+ return r;
|
|
211
|
+}
|
|
212
|
+
|
191
|
213
|
/**
|
192
|
214
|
* Adds an 'alt' class to every other visible row in the specified table.
|
193
|
215
|
*
|
|
@@ -309,6 +331,70 @@ function drawCategoryPieChart(included, incoming) {
|
309
|
331
|
});
|
310
|
332
|
}
|
311
|
333
|
|
|
334
|
+/**
|
|
335
|
+ * Calculates repeat transactions within the specified data.
|
|
336
|
+ *
|
|
337
|
+ * @param data The data to be analysed
|
|
338
|
+ */
|
|
339
|
+function calculateRepeatTransactions(data) {
|
|
340
|
+ $('#repeats').show();
|
|
341
|
+ $('#repeats tr.data').remove();
|
|
342
|
+ var table = $('#repeats table');
|
|
343
|
+
|
|
344
|
+ var descs = {};
|
|
345
|
+
|
|
346
|
+ $.each(data, function() {
|
|
347
|
+ if (!descs[this.Description]) { descs[this.Description] = []; }
|
|
348
|
+ descs[this.Description].push(this);
|
|
349
|
+ });
|
|
350
|
+
|
|
351
|
+ var monthTotal = 0;
|
|
352
|
+ $.each(descs, function(desc) {
|
|
353
|
+ // We only care if there are at least more than 2
|
|
354
|
+ if (this.length < 3) { return; }
|
|
355
|
+
|
|
356
|
+ var lastTime = 0;
|
|
357
|
+ var differences = [];
|
|
358
|
+ var amounts = [];
|
|
359
|
+
|
|
360
|
+ $.each(this, function() {
|
|
361
|
+ var time = new Date(this.Date.date).getTime();
|
|
362
|
+ lastTime > 0 && differences.push(time - lastTime);
|
|
363
|
+ lastTime = time;
|
|
364
|
+ amounts.push(this.Amount);
|
|
365
|
+ });
|
|
366
|
+
|
|
367
|
+ var average = getAverage(differences);
|
|
368
|
+ var averageAmount = getAverage(amounts);
|
|
369
|
+
|
|
370
|
+ // I may have just made this metric up. Sue me.
|
|
371
|
+ var stability = average.deviation / average.mean;
|
|
372
|
+ var periodInDays = average.mean / (1000 * 60 * 60 * 24);
|
|
373
|
+
|
|
374
|
+ if (stability < 0.5) {
|
|
375
|
+ // Seems quite reliable...
|
|
376
|
+ if ((periodInDays >= 5 && periodInDays <= 9) || (periodInDays >= 27 && periodInDays <= 32)) {
|
|
377
|
+ // Roughly weekly or monthly
|
|
378
|
+ var monthValue = (periodInDays <= 9 ? 4 : 1) * averageAmount.mean;
|
|
379
|
+
|
|
380
|
+ var tr = $('<tr class="data"/>').appendTo(table);
|
|
381
|
+ $('<td/>').text(desc).appendTo(tr);
|
|
382
|
+ $('<td/>').text(this[0].Category ? this[0].Category : 'Unsorted').appendTo(tr);
|
|
383
|
+ $('<td/>').text(periodInDays <= 9 ? 'Weekly' : 'Monthly').appendTo(tr);
|
|
384
|
+ $('<td class="amount"/>').text(averageAmount.mean.toCurrency()).appendTo(tr);
|
|
385
|
+ $('<td class="amount"/>').text(monthValue.toCurrency()).appendTo(tr);
|
|
386
|
+
|
|
387
|
+ monthTotal += monthValue;
|
|
388
|
+ }
|
|
389
|
+ }
|
|
390
|
+ });
|
|
391
|
+
|
|
392
|
+ colourTableRows(table);
|
|
393
|
+ var tr = $('<tr/>').addClass('data total').appendTo(table);
|
|
394
|
+ $('<th colspan="4" class="total">Total</th>').appendTo(tr);
|
|
395
|
+ $('<td class="amount"></td>').text(monthTotal.toCurrency()).appendTo(tr);
|
|
396
|
+}
|
|
397
|
+
|
312
|
398
|
/**
|
313
|
399
|
* Displays transactions and draws a category pie chart for the specified
|
314
|
400
|
* date range. Note that dates have a granularity of a month.
|
|
@@ -385,6 +471,7 @@ function showSelectedMonths(start, end, incoming, outgoing, categoryFilter, expa
|
385
|
471
|
|
386
|
472
|
colourTableRows(table);
|
387
|
473
|
drawCategoryPieChart(included, incoming);
|
|
474
|
+ calculateRepeatTransactions(included);
|
388
|
475
|
}
|
389
|
476
|
|
390
|
477
|
$(function() {
|