Skip to content

Commit

Permalink
Merge pull request #1 from eyra/update_copy
Browse files Browse the repository at this point in the history
Update copy
  • Loading branch information
vloothuis committed Dec 2, 2023
2 parents 3fd626a + 7b707aa commit 4e83727
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 70 deletions.
Binary file modified public/port-0.0.0-py3-none-any.whl
Binary file not shown.
Binary file modified src/framework/processing/py/dist/port-0.0.0-py3-none-any.whl
Binary file not shown.
Binary file not shown.
119 changes: 62 additions & 57 deletions src/framework/processing/py/port/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pandas as pd

import port.api.props as props
from port.api.commands import (CommandSystemDonate, CommandUIRender)
from port.api.commands import CommandSystemDonate, CommandUIRender

ExtractionResult = namedtuple("ExtractionResult", ["id", "title", "data_frame"])

Expand All @@ -17,9 +17,11 @@
class FileInZipNotFoundError(Exception):
"""Raised when a specific file is not found within the ZIP archive."""


class EmptyHealthDataError(Exception):
"""Raised when there are no health data records in the XML."""


class InvalidXMLError(Exception):
"""Raised when the XML input is invalid or empty."""

Expand All @@ -29,15 +31,19 @@ def __init__(self, callback):
self.callback = callback

def startElement(self, tag, attributes):
if tag == "Record" and attributes["type"] == "HKQuantityTypeIdentifierStepCount":
if (
tag == "Record"
and attributes["type"] == "HKQuantityTypeIdentifierStepCount"
):
value = int(attributes["value"])
start_date = self.parse_naive_datetime(attributes["startDate"])
self.callback(value, start_date)

def parse_naive_datetime(self, date_str):
dt = datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S %z')
dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S %z")
return dt.replace(tzinfo=None)


class StepCountCallback:
def __init__(self):
self.start_times = []
Expand All @@ -50,7 +56,8 @@ def __call__(self, value, start_date):
self.start_times.append(start_date)

def to_dataframe(self):
return pd.DataFrame({'Start Time': self.start_times, 'Steps': self.steps})
return pd.DataFrame({"Start Time": self.start_times, "Aantal stappen": self.steps})


def parse_health_data(file_obj):
callback_instance = StepCountCallback()
Expand All @@ -75,42 +82,41 @@ def parse_health_data(file_obj):
def aggregate_daily_steps(file_obj):
# Parse the XML to get initial DataFrame
df = parse_health_data(file_obj)
# Extract just the date from 'Start Time' column
df['Date'] = df['Start Time'].dt.strftime("%Y-%m-%d")
# Group by this date and sum the steps
daily_steps = df.groupby('Date')['Steps'].sum().reset_index()

# Extract just the from 'Start Time' column
df["Datum"] = df["Start Time"].dt.strftime("%Y-%m-%d")

# Group by this and sum the steps
daily_steps = df.groupby("Datum")["Aantal stappen"].sum().reset_index()

return daily_steps


@contextmanager
def open_export_zip(zip_path, file_name="apple_health_export/export.xml"):
archive = zipfile.ZipFile(zip_path, 'r')
archive = zipfile.ZipFile(zip_path, "r")
if file_name not in archive.namelist():
archive.close()
raise FileInZipNotFoundError(f"'{file_name}' was not found in the ZIP archive.")

try:
yield archive.open(file_name)
finally:
archive.close()


def aggregate_steps_from_zip(zip_path):
with open_export_zip(zip_path) as f:
return aggregate_daily_steps(f)



def extract_daily_steps_from_zip(zip_path):
step_data = aggregate_steps_from_zip(zip_path)
return ExtractionResult(
"ihealth_step_counts",
props.Translatable(
{"en": "Steps", "nl": "Stappen"}
),
pd.DataFrame(step_data),
)

"ihealth_step_counts",
props.Translatable({"en": "Number of steps", "nl": "Aantal stappen"}),
pd.DataFrame(step_data),
)


def process(sessionId):
Expand All @@ -122,19 +128,23 @@ def process(sessionId):
# STEP 1: select the file
data = None
while True:
promptFile = prompt_file( )
promptFile = prompt_file()
fileResult = yield render_donation_page(promptFile, 33)
if fileResult.__type__ == 'PayloadString':
if fileResult.__type__ == "PayloadString":
meta_data.append(("debug", f"extracting file"))
extractionResult = extract_daily_steps_from_zip(fileResult.value)
if extractionResult != 'invalid':
meta_data.append(("debug", f"extraction successful, go to consent form"))
if extractionResult != "invalid":
meta_data.append(
("debug", f"extraction successful, go to consent form")
)
data = extractionResult
break
else:
meta_data.append(("debug", f"prompt confirmation to retry file selection"))
meta_data.append(
("debug", f"prompt confirmation to retry file selection")
)
retry_result = yield render_donation_page(retry_confirmation(), 33)
if retry_result.__type__ == 'PayloadTrue':
if retry_result.__type__ == "PayloadTrue":
meta_data.append(("debug", f"skip due to invalid file"))
continue
else:
Expand All @@ -161,54 +171,49 @@ def render_end_page():
return CommandUIRender(page)


def render_donation_page( body, progress):
header = props.PropsUIHeader(props.Translatable({
"en": "ihealth",
"nl": "ihealth"
}))
def render_donation_page(body, progress):
header = props.PropsUIHeader(
props.Translatable({"en": "Apple Health", "nl": "Apple Health"})
)

footer = props.PropsUIFooter(progress)
page = props.PropsUIPageDonation("ihealth", header, body, footer)
return CommandUIRender(page)


def retry_confirmation():
text = props.Translatable({
"en": f"Unfortunately, we cannot process your file. Continue, if you are sure that you selected the right file. Try again to select a different file.",
"nl": f"Helaas, kunnen we uw bestand niet verwerken. Weet u zeker dat u het juiste bestand heeft gekozen? Ga dan verder. Probeer opnieuw als u een ander bestand wilt kiezen."
})
ok = props.Translatable({
"en": "Try again",
"nl": "Probeer opnieuw"
})
cancel = props.Translatable({
"en": "Continue",
"nl": "Verder"
})
text = props.Translatable(
{
"en": f"Unfortunately, we cannot process your file. Continue, if you are sure that you selected the right file. Try again to select a different file.",
"nl": f"Helaas, kunnen we uw bestand niet verwerken. Weet u zeker dat u het juiste bestand heeft gekozen? Ga dan verder. Probeer opnieuw als u een ander bestand wilt kiezen.",
}
)
ok = props.Translatable({"en": "Try again", "nl": "Probeer opnieuw"})
cancel = props.Translatable({"en": "Continue", "nl": "Verder"})
return props.PropsUIPromptConfirm(text, ok, cancel)


def prompt_file():
description = props.Translatable({
"en": f"Please follow the download instructions and choose the file that you stored on your device. Click “Skip” at the right bottom, if you do not have a file. ",
"nl": f"Volg de download instructies en kies het bestand dat u opgeslagen heeft op uw apparaat. Als u geen bestand heeft klik dan op “Overslaan” rechts onder."
})
description = props.Translatable(
{
"en": f"Click 'Choose file' to choose the file that you received from Apple. If you click 'Continue', the data that is required for research is extracted from your file.",
"nl": f"Klik op ‘Kies bestand’ om het bestand dat u ontvangen hebt van Apple te kiezen. Als u op 'Verder' klikt worden de gegevens die nodig zijn voor het onderzoek uit uw bestand gehaald.",
}
)

return props.PropsUIPromptFileInput(description, "application/zip")


def prompt_consent(table, meta_data):
log_title = props.Translatable({
"en": "Log messages",
"nl": "Log berichten"
})
log_title = props.Translatable({"en": "Log messages", "nl": "Log berichten"})

tables = [
props.PropsUIPromptConsentFormTable(table.id, table.title, table.data_frame)

]
props.PropsUIPromptConsentFormTable(table.id, table.title, table.data_frame)
]
meta_frame = pd.DataFrame(meta_data, columns=["type", "message"])
meta_table = props.PropsUIPromptConsentFormTable("log_messages", log_title, meta_frame)
meta_table = props.PropsUIPromptConsentFormTable(
"log_messages", log_title, meta_frame
)
return props.PropsUIPromptConsentForm(tables, [meta_table])


Expand Down
Binary file not shown.
31 changes: 19 additions & 12 deletions src/framework/processing/py/tests/script_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@
</HealthData>
"""


def create_test_zip(file_name="apple_health_export/export.xml"):
"""Utility function to create a test ZIP file in-memory using BytesIO."""
zip_buffer = io.BytesIO()

with zipfile.ZipFile(zip_buffer, 'w') as test_zip:
with zipfile.ZipFile(zip_buffer, "w") as test_zip:
test_zip.writestr(file_name, SAMPLE_XML)

# Reset buffer position to the beginning
zip_buffer.seek(0)
return zip_buffer


def test_aggregate_typical_case():
xml_data = """
<HealthData locale="en_NL">
Expand All @@ -36,10 +38,11 @@ def test_aggregate_typical_case():
</HealthData>
"""
df = aggregate_daily_steps(io.StringIO(xml_data))
assert df.iloc[0]['Date'] == '2023-06-24'
assert df.iloc[0]['Steps'] == 5
assert df.iloc[1]['Date'] == '2023-06-25'
assert df.iloc[1]['Steps'] == 18
assert df.iloc[0]["Datum"] == "2023-06-24"
assert df.iloc[0]["Aantal stappen"] == 5
assert df.iloc[1]["Datum"] == "2023-06-25"
assert df.iloc[1]["Aantal stappen"] == 18


def test_aggregate_same_day():
xml_data = """
Expand All @@ -50,8 +53,8 @@ def test_aggregate_same_day():
"""
df = aggregate_daily_steps(io.StringIO(xml_data))
assert len(df) == 1
assert df.iloc[0]['Date'] == '2023-06-25'
assert df.iloc[0]['Steps'] == 40
assert df.iloc[0]["Datum"] == "2023-06-25"
assert df.iloc[0]["Aantal stappen"] == 40


def test_filters_out_data_based_on_date():
Expand All @@ -64,8 +67,9 @@ def test_filters_out_data_based_on_date():
"""
df = aggregate_daily_steps(io.StringIO(xml_data))
assert len(df) == 1
assert df.iloc[0]['Date'] == '2017-01-01'
assert df.iloc[0]['Steps'] == 3
assert df.iloc[0]["Datum"] == "2017-01-01"
assert df.iloc[0]["Aantal stappen"] == 3


def test_no_records():
xml_data = """
Expand All @@ -75,6 +79,7 @@ def test_no_records():
with pytest.raises(EmptyHealthDataError):
aggregate_daily_steps(io.StringIO(xml_data))


def test_empty_input():
xml_data = ""
with pytest.raises(InvalidXMLError):
Expand All @@ -88,15 +93,17 @@ def test_open_export_zip_valid():
content = f.read().decode()
assert SAMPLE_XML in content


def test_open_export_zip_invalid_file():
zip_buffer = create_test_zip("other.xml")

with pytest.raises(FileInZipNotFoundError):
with open_export_zip(zip_buffer) as f:
pass


def test_aggregate_steps_from_zip():
zip_buffer = create_test_zip()

df = aggregate_steps_from_zip(zip_buffer) # using default function
assert not df.empty
assert not df.empty
2 changes: 1 addition & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import FakeStorage from './fake_storage'

const rootElement = document.getElementById('root') as HTMLElement

const locale = 'en'
const locale = 'nl'
const workerFile = new URL('./framework/processing/py_worker.js', import.meta.url)
const worker = new Worker(workerFile)

Expand Down

0 comments on commit 4e83727

Please sign in to comment.