use cachew

This commit is contained in:
Dima Gerasimov 2019-08-04 12:55:12 +01:00
parent 2ba0ad6f2b
commit 8c5bdf0603

View file

@ -1,20 +1,25 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from datetime import datetime, time import json
from pathlib import Path
from functools import lru_cache
import logging import logging
from collections import OrderedDict as odict from collections import OrderedDict as odict
from datetime import timedelta, datetime, date from dataclasses import dataclass
from typing import List, Dict, Iterator, NamedTuple from datetime import date, datetime, time, timedelta
import json from functools import lru_cache
import pytz from pathlib import Path
from typing import Dict, Iterator, List, NamedTuple
from kython import cproperty, timed, group_by_key import kython
import pytz
from kython import cproperty, group_by_key
from cachew import cachew
def get_logger(): def get_logger():
return logging.getLogger('emfit-provider') return logging.getLogger('emfit-provider')
timed = lambda f: kython.timed(f, logger=get_logger())
def hhmm(minutes): def hhmm(minutes):
return '{:02d}:{:02d}'.format(*divmod(minutes, 60)) return '{:02d}:{:02d}'.format(*divmod(minutes, 60))
@ -29,8 +34,9 @@ EXCLUDED = [
AWAKE = 4 AWAKE = 4
# TODO use tz provider for that? although emfit is always in london... Sid = str
# TODO FIXME use tz provider for that? although emfit is always in london...
_TZ = pytz.timezone('Europe/London') _TZ = pytz.timezone('Europe/London')
def fromts(ts) -> datetime: def fromts(ts) -> datetime:
@ -38,7 +44,23 @@ def fromts(ts) -> datetime:
return _TZ.localize(dt) return _TZ.localize(dt)
class Emfit: class Mixin:
@property
# ok, I guess that's reasonable way of defining sleep date
def date(self):
return self.end.date()
@cproperty
def time_in_bed(self):
return int((self.sleep_end - self.sleep_start).total_seconds()) // 60
@property
def recovery(self):
return self.hrv_morning - self.hrv_evening
# TODO def use multiple threads for that..
class EmfitOld(Mixin):
def __init__(self, sid: str, jj): def __init__(self, sid: str, jj):
self.sid = sid self.sid = sid
self.jj = jj self.jj = jj
@ -54,11 +76,6 @@ class Emfit:
def hrv_evening(self): def hrv_evening(self):
return self.jj['hrv_rmssd_evening'] return self.jj['hrv_rmssd_evening']
# ok, I guess that's reasonable way of defining sleep date
@property
def date(self):
return self.end.date()
""" """
Bed time, not necessarily sleep Bed time, not necessarily sleep
""" """
@ -104,10 +121,6 @@ class Emfit:
# 'sleep_epoch_datapoints' # 'sleep_epoch_datapoints'
# [[timestamp, number]] # [[timestamp, number]]
@cproperty
def time_in_bed(self):
return int((self.sleep_end - self.sleep_start).total_seconds()) // 60
# so it's actual sleep, without awake # so it's actual sleep, without awake
# ok, so I need time_asleep # ok, so I need time_asleep
@property @property
@ -122,10 +135,6 @@ class Emfit:
def hrv_hf(self): def hrv_hf(self):
return self.jj['hrv_hf'] return self.jj['hrv_hf']
@property
def recovery(self):
return self.hrv_morning - self.hrv_evening
@property @property
def summary(self): def summary(self):
return f"""in bed for {hhmm(self.time_in_bed)} return f"""in bed for {hhmm(self.time_in_bed)}
@ -252,19 +261,43 @@ recovery: {self.recovery:3.0f}
return covered / expected * 100 return covered / expected * 100
# right, so dataclass is better because you can use mixins
@dataclass(eq=True, frozen=True)
class Emfit(Mixin):
sid: Sid
hrv_morning: float
hrv_evening: float
start: datetime
end : datetime
sleep_start: datetime
sleep_end : datetime
sleep_hr_coverage: float
measured_hr_avg: float
@lru_cache(1000) # TODO hmm. should I configure it dynamically??? @classmethod
def get_emfit(sid: str, f: Path) -> Emfit: def make(cls, em) -> Iterator['Emfit']:
return Emfit(sid=sid, jj=json.loads(f.read_text())) # TODO FIXME res?
logger = get_logger()
if em.epochs is None:
logger.error('%s (on %s) got None in epochs! ignoring', em.sid, em.date)
return
yield cls(**{
k: getattr(em, k) for k in Emfit.__annotations__
})
# TODO very nice!
@cachew
def iter_datas() -> Iterator[Emfit]: def iter_datas() -> Iterator[Emfit]:
for f in PATH.glob('*.json'): for f in sorted(PATH.glob('*.json')):
sid = f.stem sid = f.stem
if sid in EXCLUDED: if sid in EXCLUDED:
continue continue
yield get_emfit(sid, f) em = EmfitOld(sid=sid, jj=json.loads(f.read_text()))
yield from Emfit.make(em)
def get_datas() -> List[Emfit]: def get_datas() -> List[Emfit]:
@ -282,11 +315,6 @@ def by_night() -> Dict[date, Emfit]:
logger.warning("multiple sleeps per night, not handled yet: %s", sleeps) logger.warning("multiple sleeps per night, not handled yet: %s", sleeps)
continue continue
[s] = sleeps [s] = sleeps
if s.epochs is None:
logger.error('%s (on %s) got None in epochs! ignoring', s.sid, dd)
continue
res[s.date] = s res[s.date] = s
return res return res
@ -319,10 +347,10 @@ def test_tz():
def main(): def main():
from kython.klogging import setup_logzero
setup_logzero(get_logger(), level=logging.DEBUG)
for k, v in by_night().items(): for k, v in by_night().items():
print(k, v.start, v.end) print(k, v.start, v.end)
if __name__ == '__main__': if __name__ == '__main__':
from kython.klogging import setup_logzero
setup_logzero(get_logger())
main() main()