Skip to content

Commit db85e73

Browse files
committed
add loader for apple health data
1 parent 159c1e6 commit db85e73

File tree

5 files changed

+114
-0
lines changed

5 files changed

+114
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Mac OS folder attribution file
2+
.DS_Store
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]

README.md

+16
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Make everything a GitHub svg poster and [skyline](https://skyline.github.com/)!
5050
- **[Covid](#Covid)**
5151
- **[Todoist](#Todoist)**
5252
- **[开言英语](#开言英语)**
53+
- **[Apple Health](#AppleHealth)**
5354

5455
## 下载
5556

@@ -634,6 +635,21 @@ github_poster openlanguage --year 2021-2022 --openlanguage_user_name "you accoun
634635
```
635636
</details>
636637

638+
### AppleHealth
639+
<details>
640+
<summary>Make <code> Apple Health </code> GitHub poster</summary>
641+
642+
Apple Health 里有丰富的数据,此 loader 暂时只支持 Apple Watch Activity 里的三大项,即 Move,Exercise,Stand。但理论上任何 Apple Health 里的数据都能支持。
643+
打开 Health App, 点击右上方头像,选择 Export All Health Data, 将所得压缩包拷贝到 `IN-FOLDER` 后解压,会得到一个 `apple_health_export` 文件夹。之后运行:
644+
<br>
645+
646+
```
647+
python3 -m github_poster apple_health --year 2015-2021 --apple_health_record_type <move, exercise, stand> --me "your name"
648+
or
649+
github_poster apple_health --year 2015-2021 --apple_health_record_type <move, exercise, stand> --me "your name"
650+
```
651+
</details>
652+
637653
# 参与项目
638654

639655
- 任何 Issues PR 均欢迎。

github_poster/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,5 @@
4747
"weread": "WeRead",
4848
"covid": "COVID-19",
4949
"todoist": "Todoist",
50+
"apple": "AppleHealth",
5051
}

github_poster/loader/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from github_poster.loader.apple_health_loader import AppleHealthLoader
12
from github_poster.loader.bbdc_loader import BBDCLoader
23
from github_poster.loader.bilibili_loader import BilibiliLoader
34
from github_poster.loader.cichang_loader import CiChangLoader
@@ -29,6 +30,7 @@
2930
from github_poster.loader.youtube_loader import YouTubeLoader
3031

3132
LOADER_DICT = {
33+
"apple_health": AppleHealthLoader,
3234
"bbdc": BBDCLoader,
3335
"duolingo": DuolingoLoader,
3436
"shanbay": ShanBayLoader,
@@ -61,6 +63,7 @@
6163
}
6264

6365
__all__ = (
66+
"AppleHealthLoader",
6467
"BilibiliLoader",
6568
"CiChangLoader",
6669
"Dota2Loader",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import os
2+
import xml.etree.ElementTree as ET
3+
from collections import defaultdict, namedtuple
4+
5+
import pendulum
6+
7+
from github_poster.loader.base_loader import BaseLoader
8+
9+
# func is a lambda that converts the "value" attribute of the record to a numeric value.
10+
RecordMetadata = namedtuple("RecordMetadata", ["type", "unit", "track_color", "func"])
11+
12+
13+
SUPPORTED_HEALTH_RECORD_TYPES = {
14+
"move": RecordMetadata(
15+
"HKQuantityTypeIdentifierActiveEnergyBurned",
16+
"kCal",
17+
"#ED619C",
18+
lambda x: float(x),
19+
),
20+
"exercise": RecordMetadata(
21+
"HKQuantityTypeIdentifierAppleExerciseTime", "mins", "#D7FD37", lambda x: int(x)
22+
),
23+
"stand": RecordMetadata(
24+
"HKCategoryTypeIdentifierAppleStandHour",
25+
"hours",
26+
"#62F90B",
27+
lambda x: 1 if "HKCategoryValueAppleStandHourStood" else 0,
28+
),
29+
}
30+
31+
32+
class AppleHealthLoader(BaseLoader):
33+
def __init__(self, from_year, to_year, _type, **kwargs):
34+
super().__init__(from_year, to_year, _type)
35+
self.number_by_date_dict = defaultdict(int)
36+
self.apple_health_export_file = kwargs.get("apple_health_export_file")
37+
self.apple_health_record_type = kwargs.get("apple_health_record_type")
38+
39+
@classmethod
40+
def add_loader_arguments(cls, parser, optional):
41+
parser.add_argument(
42+
"--apple_health_export_file",
43+
dest="apple_health_export_file",
44+
type=str,
45+
default=os.path.join("IN_FOLDER", "apple_health_export", "export.xml"),
46+
help="Apple Health export file path",
47+
)
48+
parser.add_argument(
49+
"--apple_health_record_type",
50+
dest="apple_health_record_type",
51+
choices=SUPPORTED_HEALTH_RECORD_TYPES.keys(),
52+
default="move",
53+
help="Apple Health Record Type",
54+
)
55+
56+
def make_track_dict(self):
57+
record_metadata = SUPPORTED_HEALTH_RECORD_TYPES[self.apple_health_record_type]
58+
self.__class__.unit = record_metadata.unit
59+
self.__class__.track_color = record_metadata.track_color
60+
61+
in_target_section = False
62+
for _, elem in ET.iterparse(self.apple_health_export_file, events=["end"]):
63+
if elem.tag != "Record":
64+
continue
65+
66+
if elem.attrib["type"] == record_metadata.type:
67+
in_target_section = True
68+
create_date = pendulum.from_format(
69+
elem.attrib["creationDate"], "YYYY-MM-DD HH:mm:ss ZZ"
70+
)
71+
if (
72+
create_date.year >= self.from_year
73+
and create_date.year <= self.to_year
74+
):
75+
self.number_by_date_dict[
76+
create_date.to_date_string()
77+
] += record_metadata.func(elem.attrib["value"])
78+
elif in_target_section:
79+
break
80+
81+
elem.clear()
82+
83+
self.number_by_date_dict = {
84+
k: int(v) for k, v in self.number_by_date_dict.items()
85+
}
86+
self.number_list = list(self.number_by_date_dict.values())
87+
88+
def get_all_track_data(self):
89+
self.make_track_dict()
90+
self.make_special_number()
91+
return self.number_by_date_dict, self.year_list

0 commit comments

Comments
 (0)