1
0
mirror of https://github.com/elisspace/FxLifeSheet.git synced 2026-08-29 15:44:00 +00:00

Implement initial bucketing

This commit is contained in:
Felix Krause
2021-04-19 01:24:15 +02:00
parent 68b4cc2979
commit fc40247640
4 changed files with 158 additions and 50 deletions

View File

@@ -45,52 +45,59 @@ class API
return raw_data.group_and_count(:key).order_by(:count).reverse.to_a
end
def bucket(by:, value:, start_date:)
def bucket_options_list(by:, start_date:)
raise "`start_date` must be in format '2019-04'" unless start_date.match(/\d\d\d\d\-\d\d/)
start_timestamp = Date.strptime(start_date, "%Y-%m").strftime("%Q")
other_key = "fishoilIntake"
buckets = {}
res = database.fetch("
SELECT value, count(*)
FROM raw_data
WHERE key=? AND timestamp > ?
GROUP BY value
", by, start_timestamp)
return res.to_a.reverse # have on by default
end
def bucket(by:, start_date:)
raise "`start_date` must be in format '2019-04'" unless start_date.match(/\d\d\d\d\-\d\d/)
start_timestamp = Date.strptime(start_date, "%Y-%m").strftime("%Q")
flat = database.fetch("
SELECT
raw_data.value AS bucket,
(
SELECT
rd.value
rd.value AS bucket,
nrd.key AS other_key,
AVG(nrd.value::numeric) AS avg_value,
COUNT(nrd.id) as count
FROM raw_data rd
WHERE key = 'fishoilIntake'
AND timestamp > 1554076800000
ORDER BY abs(rd.timestamp - raw_data.timestamp) ASC
LIMIT 1
)
FROM raw_data raw_data
WHERE key = 'gym' AND timestamp > 1554076800000
")
INNER JOIN raw_data nrd ON (
(nrd.type != 'text') AND
abs(rd.timestamp - nrd.timestamp) < 20000000 /* 10000 is one minute */
)
WHERE rd.key = ? AND rd.timestamp > ?
GROUP BY bucket, other_key
ORDER BY other_key, bucket
", by, start_timestamp).to_a
# SELECT
# rd.value AS bucket,
# nrd.key AS other_key,
# AVG(nrd.value::numeric) AS avg_value,
# COUNT(nrd.id) as count
# FROM raw_data rd
# INNER JOIN raw_data nrd ON (
# (nrd.type != 'text') AND
# abs(rd.timestamp - nrd.timestamp) < 20000000 /* 10000 is one minute */
# )
# WHERE rd.key = 'headache' AND rd.timestamp > 1554076800000
# GROUP BY bucket, other_key
# ORDER BY other_key, bucket
# Group it properly, easier to just do that in Ruby
structured = {}
flat.each do |row|
next if row[:avg_value].nil? # some rows can be nil
next if row[:other_key].include?("swarmLocation") || row[:other_key].include?("locationL") || ["weight"].include?(row[:other_key])
# TODO: Limit that the entry can't be more than 24 hours away
flat.each do |current_row|
buckets[current_row[:bucket]] ||= []
buckets[current_row[:bucket]] << current_row[:value].to_f
structured[row[:other_key]] ||= {}
structured[row[:other_key]][row[:bucket]] = {
value: row[:avg_value].truncate(5).to_s('F').to_f, # convert from BigFloat to float,
count: row[:count]
}
end
grouped = buckets.collect { |k, v| [k, v.sum / v.count]}.to_h
return grouped
# Remove the useless ones (e.g. only one value, not large enough buckets)
structured.delete_if do |key, value|
value.count < 2 ||
value.find_all { |k, r| r[:count] > 30 }.count < 2
end
return structured
end
private
@@ -167,7 +174,6 @@ end
# GROUP BY raw_data.value
# SELECT
# rd.value AS bucket,
# nrd.key AS other_key,
@@ -183,3 +189,18 @@ end
# ORDER BY other_key, bucket
# flat = database.fetch("
# SELECT
# raw_data.value AS bucket,
# (
# SELECT
# rd.value
# FROM raw_data rd
# WHERE key = 'fishoilIntake'
# AND timestamp > 1554076800000
# ORDER BY abs(rd.timestamp - raw_data.timestamp) ASC
# LIMIT 1
# )
# FROM raw_data raw_data
# WHERE key = 'gym' AND timestamp > 1554076800000
# ")

View File

@@ -63,14 +63,6 @@
</select>
</td>
</tr>
<tr>
<td>Bucket By</td>
<td>
<select id="bucket-by" class="keys" onchange="updateBucketBy()">
<option value="" selected="selected">None</option>
</select>
</td>
</tr>
<tr>
<td>Start date</td>
<td><input type="text" value="2019-04" id="start-date" name="start-date" onchange="reloadAllData()" /></td>
@@ -79,6 +71,13 @@
</span>
</form>
<div id='myGraph' />
<br />
<h1>Buckets</h1>
<select class="keys" id="bucket-key" onchange="updateBucket()"></select>
<select class="bucket-by-option" id="bucket-by-option-1" onchange="updateBucketByOption()"></select>
<select class="bucket-by-option" id="bucket-by-option-2" onchange="updateBucketByOption()"></select>
<div id='bucketGraph' />
</body>
<script type="text/javascript" src="/frontend.js"></script>

View File

@@ -1,4 +1,4 @@
const host = 'http://127.0.0.1:4567';
const host = 'http://127.0.0.1:8080';
let keys = [];
const allData = [];
let groupBy = 'month';
@@ -14,7 +14,6 @@ for (const currentIndex of Array(5).keys()) {
if (currentIndex > 0) { current.yaxis = `y${currentIndex + 1}`; }
allData.push(current);
}
console.log(allData);
const layout = {
title: 'Life Sheet Data',
@@ -64,6 +63,26 @@ const layout = {
Plotly.newPlot('myGraph', allData, layout);
const bucketLayout = {
title: 'Life Sheet Buckets',
barmode: 'group',
yaxis: {
range: [-1, 1]
}
}
const allBucketData = [{
x: [],
y: [],
marker: {
color: '#C8A2C8',
line: {
width: 2.5
}
},
type: "bar"
}]
Plotly.newPlot('bucketGraph', allBucketData, bucketLayout);
function loadKeys(callback) {
httpGetAsync(`${host}/keys`, (data) => {
keys = data;
@@ -121,7 +140,7 @@ function reloadIndex(index) {
Plotly.redraw('myGraph');
console.log(allData);
console.log(layout);
// console.log(layout);
});
} else {
allData[index].y = [];
@@ -129,6 +148,63 @@ function reloadIndex(index) {
}
}
let currentBucketData = null;
function updateBucket() {
bucket = document.getElementById("bucket-key").value
httpGetAsync(`${host}/bucket_options_list?by=${bucket}`, (data) => {
selects = document.getElementsByClassName('bucket-by-option');
for (let i = 0; i < selects.length; i++) {
selects[i].innerHTML = ""
data.forEach((row) => {
const opt = document.createElement('option');
opt.value = row.value;
opt.innerHTML = `${row.value} (${row.count})`;
selects[i].appendChild(opt);
});
selects[i].selectedIndex = i
}
updateBucketByOption();
})
httpGetAsync(`${host}/bucket?by=${bucket}`, (data) => {
currentBucketData = data;
console.log(data)
updateBucketByOption();
});
}
function updateBucketByOption() {
if (!currentBucketData) { return; }
bucket1 = document.getElementById("bucket-by-option-1").value
bucket2 = document.getElementById("bucket-by-option-2").value
const dataToRender = []
for (var key in currentBucketData) {
val = currentBucketData[key]
let diff = parseFloat(val[bucket1]["value"] - val[bucket2]["value"].toFixed(5))
if (Math.abs(diff) > 0.2) {
dataToRender.push({
"key": key,
"bucket1": val[bucket1],
"bucket2": val[bucket2],
"diff": diff
})
}
}
dataToRender.sort(function(a, b) {
return a.diff - b.diff;
})
console.log(dataToRender)
allBucketData[0]["x"] = dataToRender.map(({ key }) => key);
allBucketData[0]["y"] = dataToRender.map(({ diff }) => diff);
console.log(allBucketData)
Plotly.redraw('bucketGraph');
}
function httpGetAsync(theUrl, callback) {
const xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
@@ -146,6 +222,10 @@ loadKeys(() => {
updateKeyForIndex('sleepDurationWithings', 2);
updateKeyForIndex('bedTime', 3);
updateKeyForIndex('gym', 4);
document.getElementById("bucket-key").value = "headache"
updateBucket();
// reloadAllData();
// updateKeyForIndex(keys[0].key, 0);

View File

@@ -29,12 +29,20 @@ get "/keys" do
JSON.pretty_generate(api.list_keys)
end
get "/bucket_options_list" do
json_response
JSON.pretty_generate(api.bucket_options_list(
by: params.fetch("by"),
start_date: params.fetch("start_date", ENV["DEFAULT_MIN_DATE"].strip)
))
end
get "/bucket" do
json_response
JSON.pretty_generate(api.bucket(
by: "gym",
value: "fishoilIntake",
by: params.fetch("by"),
start_date: params.fetch("start_date", ENV["DEFAULT_MIN_DATE"].strip)
))
end