my.jawbone: minor cleanup & refactoring, proper error propagation
This commit is contained in:
parent
99e50f0afe
commit
63b848087d
7 changed files with 108 additions and 86 deletions
|
@ -53,11 +53,10 @@ def from_orgmode() -> Iterator[Result]:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def dataframe():
|
def make_dataframe(data: Iterator[Result]):
|
||||||
import pandas as pd # type: ignore
|
import pandas as pd # type: ignore
|
||||||
entries = from_orgmode()
|
|
||||||
def it():
|
def it():
|
||||||
for e in from_orgmode():
|
for e in data:
|
||||||
if isinstance(e, Exception):
|
if isinstance(e, Exception):
|
||||||
dt = extract_error_datetime(e)
|
dt = extract_error_datetime(e)
|
||||||
yield {
|
yield {
|
||||||
|
@ -75,6 +74,11 @@ def dataframe():
|
||||||
df.index = pd.to_datetime(df.index, utc=True)
|
df.index = pd.to_datetime(df.index, utc=True)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
|
||||||
|
def dataframe():
|
||||||
|
entries = from_orgmode()
|
||||||
|
return make_dataframe(entries)
|
||||||
|
|
||||||
# TODO move to a submodule? e.g. my.body.weight.orgmode?
|
# TODO move to a submodule? e.g. my.body.weight.orgmode?
|
||||||
# so there could be more sources
|
# so there could be more sources
|
||||||
# not sure about my.body thing though
|
# not sure about my.body thing though
|
||||||
|
|
|
@ -116,7 +116,7 @@ def extract_error_datetime(e: Exception) -> Optional[datetime]:
|
||||||
import re
|
import re
|
||||||
# TODO FIXME meh. definitely need to preserve exception args types in cachew if possible..
|
# TODO FIXME meh. definitely need to preserve exception args types in cachew if possible..
|
||||||
for x in reversed(e.args):
|
for x in reversed(e.args):
|
||||||
m = re.search(r'\d{4}.*T.*:..(\.\d{6})?(\+.....)?', x)
|
m = re.search(r'\d{4}-\d\d-\d\d(T..:..:..)?(\.\d{6})?(\+.....)?', x)
|
||||||
if m is None:
|
if m is None:
|
||||||
continue
|
continue
|
||||||
ss = m.group(0)
|
ss = m.group(0)
|
||||||
|
@ -141,3 +141,7 @@ def test_datetime_errors():
|
||||||
|
|
||||||
e3 = RuntimeError(str(['one', '2019-11-27T08:56:00', 'three']))
|
e3 = RuntimeError(str(['one', '2019-11-27T08:56:00', 'three']))
|
||||||
assert extract_error_datetime(e3) is not None
|
assert extract_error_datetime(e3) is not None
|
||||||
|
|
||||||
|
# date only
|
||||||
|
e4 = RuntimeError(str(['one', '2019-11-27', 'three']))
|
||||||
|
assert extract_error_datetime(e4) is not None
|
||||||
|
|
|
@ -11,7 +11,7 @@ from typing import Dict, List, Iterable
|
||||||
from ..core import get_files
|
from ..core import get_files
|
||||||
from ..core.common import mcachew
|
from ..core.common import mcachew
|
||||||
from ..core.cachew import cache_dir
|
from ..core.cachew import cache_dir
|
||||||
from ..core.error import Res
|
from ..core.error import Res, set_error_datetime, extract_error_datetime
|
||||||
from ..core.types import DataFrameT
|
from ..core.types import DataFrameT
|
||||||
|
|
||||||
from my.config import emfit as config
|
from my.config import emfit as config
|
||||||
|
@ -70,6 +70,7 @@ def pre_dataframe() -> Iterable[Res[Emfit]]:
|
||||||
yield r
|
yield r
|
||||||
else:
|
else:
|
||||||
err = RuntimeError(f'Multiple sleeps per night, not supported yet: {g}')
|
err = RuntimeError(f'Multiple sleeps per night, not supported yet: {g}')
|
||||||
|
set_error_datetime(err, dt=g[0].date)
|
||||||
g.clear()
|
g.clear()
|
||||||
yield err
|
yield err
|
||||||
|
|
||||||
|
@ -90,8 +91,11 @@ def dataframe() -> DataFrameT:
|
||||||
last = None
|
last = None
|
||||||
for s in pre_dataframe():
|
for s in pre_dataframe():
|
||||||
if isinstance(s, Exception):
|
if isinstance(s, Exception):
|
||||||
# todo date would be nice too?
|
edt = extract_error_datetime(s)
|
||||||
d = {'error': str(s)}
|
d = {
|
||||||
|
'date' : edt,
|
||||||
|
'error': str(s),
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
dd = s.date
|
dd = s.date
|
||||||
pday = dd - timedelta(days=1)
|
pday = dd - timedelta(days=1)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# TODO this should be integrated into dashboard
|
||||||
# def stats():
|
# def stats():
|
||||||
# for jj in iter_datas():
|
# for jj in iter_datas():
|
||||||
# # TODO fimezone??
|
# # TODO fimezone??
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List, Iterable
|
||||||
import json
|
import json
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from datetime import datetime, date, time, timedelta
|
from datetime import datetime, date, time, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
from ..core.common import LazyLogger
|
||||||
|
|
||||||
|
logger = LazyLogger(__name__)
|
||||||
|
|
||||||
from my.config import jawbone as config
|
from my.config import jawbone as config
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,9 +20,6 @@ SLEEPS_FILE = BDIR / 'sleeps.json'
|
||||||
GRAPHS_DIR = BDIR / 'graphs'
|
GRAPHS_DIR = BDIR / 'graphs'
|
||||||
|
|
||||||
|
|
||||||
def get_logger():
|
|
||||||
return logging.getLogger('jawbone-provider')
|
|
||||||
|
|
||||||
|
|
||||||
XID = str # TODO how to shared with backup thing?
|
XID = str # TODO how to shared with backup thing?
|
||||||
|
|
||||||
|
@ -32,13 +33,13 @@ class SleepEntry:
|
||||||
def __init__(self, js) -> None:
|
def __init__(self, js) -> None:
|
||||||
self.js = js
|
self.js = js
|
||||||
|
|
||||||
# TODO @memoize decorator?
|
|
||||||
@property
|
@property
|
||||||
def date_(self) -> date:
|
def date_(self) -> date:
|
||||||
return self.sleep_end.date()
|
return self.sleep_end.date()
|
||||||
|
|
||||||
def _fromts(self, ts: int) -> datetime:
|
def _fromts(self, ts: int) -> datetime:
|
||||||
return pytz.utc.localize(datetime.utcfromtimestamp(ts)).astimezone(self._tz).astimezone(self._tz)
|
return datetime.fromtimestamp(ts, tz=self._tz)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _tz(self):
|
def _tz(self):
|
||||||
return pytz.timezone(self._details['tz'])
|
return pytz.timezone(self._details['tz'])
|
||||||
|
@ -103,15 +104,63 @@ def load_sleeps() -> List[SleepEntry]:
|
||||||
return [SleepEntry(js) for js in sleeps]
|
return [SleepEntry(js) for js in sleeps]
|
||||||
|
|
||||||
|
|
||||||
|
from ..core.error import Res, set_error_datetime, extract_error_datetime
|
||||||
|
|
||||||
|
def pre_dataframe() -> Iterable[Res[SleepEntry]]:
|
||||||
|
sleeps = load_sleeps()
|
||||||
|
# todo emit error if graph doesn't exist??
|
||||||
|
sleeps = [s for s in sleeps if s.graph.exists()] # TODO careful..
|
||||||
|
from ..common import group_by_key
|
||||||
|
for dd, group in group_by_key(sleeps, key=lambda s: s.date_).items():
|
||||||
|
if len(group) == 1:
|
||||||
|
yield group[0]
|
||||||
|
else:
|
||||||
|
err = RuntimeError(f'Multiple sleeps per night, not supported yet: {group}')
|
||||||
|
set_error_datetime(err, dt=dd)
|
||||||
|
logger.exception(err)
|
||||||
|
yield err
|
||||||
|
|
||||||
|
|
||||||
|
def dataframe():
|
||||||
|
dicts: List[Dict] = []
|
||||||
|
for s in pre_dataframe():
|
||||||
|
if isinstance(s, Exception):
|
||||||
|
dt = extract_error_datetime(s)
|
||||||
|
d = {
|
||||||
|
'date' : dt,
|
||||||
|
'error': str(s),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
d = {
|
||||||
|
# TODO make sure sleep start/end are consistent with emfit? add to test as well..
|
||||||
|
# I think it makes sense to be end date as 99% of time
|
||||||
|
# or maybe I shouldn't care about this at all?
|
||||||
|
'date' : s.date_,
|
||||||
|
'sleep_start': s.sleep_start,
|
||||||
|
'sleep_end' : s.sleep_end,
|
||||||
|
'bed_time' : s.bed_time,
|
||||||
|
}
|
||||||
|
dicts.append(d)
|
||||||
|
|
||||||
|
import pandas as pd # type: ignore
|
||||||
|
return pd.DataFrame(dicts)
|
||||||
|
# TODO tz is in sleeps json
|
||||||
|
|
||||||
|
|
||||||
|
# TODO add dataframe support to stat()
|
||||||
|
def stats():
|
||||||
|
from ..core import stat
|
||||||
|
return stat(pre_dataframe)
|
||||||
|
|
||||||
|
|
||||||
|
#### NOTE: most of the stuff below is deprecated and remnants of my old code!
|
||||||
|
#### sorry for it, feel free to remove if you don't need it
|
||||||
|
|
||||||
import numpy as np # type: ignore
|
import numpy as np # type: ignore
|
||||||
import matplotlib.pyplot as plt # type: ignore
|
import matplotlib.pyplot as plt # type: ignore
|
||||||
from matplotlib.figure import Figure # type: ignore
|
from matplotlib.figure import Figure # type: ignore
|
||||||
from matplotlib.axes import Axes # type: ignore
|
from matplotlib.axes import Axes # type: ignore
|
||||||
|
|
||||||
# pip install imageio
|
|
||||||
from imageio import imread # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def hhmm(time: datetime):
|
def hhmm(time: datetime):
|
||||||
return time.strftime("%H:%M")
|
return time.strftime("%H:%M")
|
||||||
|
|
||||||
|
@ -128,6 +177,9 @@ def plot_one(sleep: SleepEntry, fig: Figure, axes: Axes, xlims=None, showtext=Tr
|
||||||
span = sleep.completed - sleep.created
|
span = sleep.completed - sleep.created
|
||||||
print(f"{sleep.xid} span: {span}")
|
print(f"{sleep.xid} span: {span}")
|
||||||
|
|
||||||
|
# pip install imageio
|
||||||
|
from imageio import imread # type: ignore
|
||||||
|
|
||||||
img = imread(sleep.graph)
|
img = imread(sleep.graph)
|
||||||
# all of them are 300x300 images apparently
|
# all of them are 300x300 images apparently
|
||||||
# span for image
|
# span for image
|
||||||
|
@ -187,28 +239,6 @@ def plot_one(sleep: SleepEntry, fig: Figure, axes: Axes, xlims=None, showtext=Tr
|
||||||
axes.text(xlims[1] - timedelta(hours=1.5), 20, str(sleep),)
|
axes.text(xlims[1] - timedelta(hours=1.5), 20, str(sleep),)
|
||||||
# plt.text(sleep.asleep(), 0, hhmm(sleep.asleep()))
|
# plt.text(sleep.asleep(), 0, hhmm(sleep.asleep()))
|
||||||
|
|
||||||
from ..common import group_by_key
|
|
||||||
|
|
||||||
def sleeps_by_date() -> Dict[date, SleepEntry]:
|
|
||||||
logger = get_logger()
|
|
||||||
|
|
||||||
sleeps = load_sleeps()
|
|
||||||
sleeps = [s for s in sleeps if s.graph.exists()] # TODO careful..
|
|
||||||
res = {}
|
|
||||||
for dd, group in group_by_key(sleeps, key=lambda s: s.date_).items():
|
|
||||||
if len(group) == 1:
|
|
||||||
res[dd] = group[0]
|
|
||||||
else:
|
|
||||||
# TODO short ones I can ignore I guess. but won't bother now
|
|
||||||
logger.error('multiple sleeps on %s: %s', dd, group)
|
|
||||||
return res
|
|
||||||
|
|
||||||
# sleeps_count = 35 # len(sleeps) # apparently MPL fails at 298 with outofmemory or something
|
|
||||||
# start = 40
|
|
||||||
# 65 is arount 1 july
|
|
||||||
# sleeps = sleeps[start: start + sleeps_count]
|
|
||||||
# sleeps = sleeps[:sleeps_count]
|
|
||||||
# dt = {k: v for k, v in dt.items() if v is not None}
|
|
||||||
|
|
||||||
# TODO not really sure it belongs here...
|
# TODO not really sure it belongs here...
|
||||||
# import melatonin
|
# import melatonin
|
||||||
|
@ -225,6 +255,7 @@ def predicate(sleep: SleepEntry):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# TODO move to dashboard
|
||||||
def plot():
|
def plot():
|
||||||
# TODO FIXME melatonin data
|
# TODO FIXME melatonin data
|
||||||
melatonin_data = {} # type: ignore[var-annotated]
|
melatonin_data = {} # type: ignore[var-annotated]
|
||||||
|
@ -264,48 +295,3 @@ def plot():
|
||||||
# plt.savefig('res.png', asp)
|
# plt.savefig('res.png', asp)
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
import pandas as pd # type: ignore
|
|
||||||
def get_dataframe():
|
|
||||||
sleeps = sleeps_by_date()
|
|
||||||
items = []
|
|
||||||
for dd, s in sleeps.items():
|
|
||||||
items.append({
|
|
||||||
'date' : dd, # TODO not sure... # TODO would also be great to sync column names...
|
|
||||||
'sleep_start': s.sleep_start,
|
|
||||||
'sleep_end' : s.sleep_end,
|
|
||||||
'bed_time' : s.bed_time,
|
|
||||||
})
|
|
||||||
# TODO tz is in sleeps json
|
|
||||||
res = pd.DataFrame(items)
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def test_tz():
|
|
||||||
sleeps = sleeps_by_date()
|
|
||||||
for s in sleeps.values():
|
|
||||||
assert s.sleep_start.tzinfo is not None
|
|
||||||
assert s.sleep_end.tzinfo is not None
|
|
||||||
|
|
||||||
for dd, exp in [
|
|
||||||
(date(year=2015, month=8 , day=28), time(hour=7, minute=20)),
|
|
||||||
(date(year=2015, month=9 , day=15), time(hour=6, minute=10)),
|
|
||||||
]:
|
|
||||||
sleep = sleeps[dd]
|
|
||||||
end = sleep.sleep_end
|
|
||||||
|
|
||||||
assert end.time() == exp
|
|
||||||
|
|
||||||
# TODO fuck. on 0909 I woke up at around 6 according to google timeline
|
|
||||||
# but according to jawbone, it was on 0910?? eh. I guess it's jus shitty tracking.
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# TODO eh. vendorize klogging already?
|
|
||||||
from kython.klogging import setup_logzero
|
|
||||||
setup_logzero(get_logger())
|
|
||||||
test_tz()
|
|
||||||
# print(get_dataframe())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# TODO
|
# TODO this should be in dashboard
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
# from kython.plotting import *
|
# from kython.plotting import *
|
||||||
from csv import DictReader
|
from csv import DictReader
|
||||||
|
|
23
tests/jawbone.py
Normal file
23
tests/jawbone.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# TODO depends on my private data... move somewhere, and exclude from CI somehow?
|
||||||
|
|
||||||
|
from datetime import date, time
|
||||||
|
|
||||||
|
from my.jawbone import sleeps_by_date
|
||||||
|
|
||||||
|
# todo private test.. move away
|
||||||
|
def test_tz():
|
||||||
|
sleeps = sleeps_by_date()
|
||||||
|
for s in sleeps.values():
|
||||||
|
assert s.sleep_start.tzinfo is not None
|
||||||
|
assert s.sleep_end.tzinfo is not None
|
||||||
|
|
||||||
|
for dd, exp in [
|
||||||
|
(date(year=2015, month=8 , day=28), time(hour=7, minute=20)),
|
||||||
|
(date(year=2015, month=9 , day=15), time(hour=6, minute=10)),
|
||||||
|
]:
|
||||||
|
sleep = sleeps[dd]
|
||||||
|
end = sleep.sleep_end
|
||||||
|
|
||||||
|
assert end.time() == exp
|
||||||
|
# TODO fuck. on 0909 I woke up at around 6 according to google timeline
|
||||||
|
# but according to jawbone, it was on 0910?? eh. I guess it's just shitty tracking.
|
Loading…
Add table
Reference in a new issue