willisapi-client 1.9.3__tar.gz → 1.9.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/PKG-INFO +17 -4
  2. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/README.md +16 -3
  3. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/__init__.py +2 -0
  4. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/__version__.py +1 -1
  5. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/api.py +1 -1
  6. willisapi_client-1.9.4/willisapi_client/services/metadata/__init__.py +4 -0
  7. willisapi_client-1.9.4/willisapi_client/services/metadata/upload.py +194 -0
  8. willisapi_client-1.9.4/willisapi_client/services/metadata/utils.py +746 -0
  9. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/willisapi_client.py +3 -0
  10. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/PKG-INFO +17 -4
  11. willisapi_client-1.9.3/willisapi_client/services/metadata/__init__.py +0 -4
  12. willisapi_client-1.9.3/willisapi_client/services/metadata/upload.py +0 -91
  13. willisapi_client-1.9.3/willisapi_client/services/metadata/utils.py +0 -350
  14. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/LICENSE +0 -0
  15. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/MANIFEST.in +0 -0
  16. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/RELEASE.md +0 -0
  17. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/requirements.txt +0 -0
  18. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/setup.cfg +0 -0
  19. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/setup.py +0 -0
  20. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/tests/test_diarize.py +0 -0
  21. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/tests/test_diarize_call_remaining.py +0 -0
  22. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/logging_setup.py +0 -0
  23. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/__init__.py +0 -0
  24. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/__init__.py +0 -0
  25. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/diarize_utils.py +0 -0
  26. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/willisdiarize.py +0 -0
  27. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/diarize/willisdiarize_call_remaining.py +0 -0
  28. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/exceptions.py +0 -0
  29. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/services/metadata/language_choices.py +0 -0
  30. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client/timer.py +0 -0
  31. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/SOURCES.txt +0 -0
  32. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/dependency_links.txt +0 -0
  33. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/not-zip-safe +0 -0
  34. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/requires.txt +0 -0
  35. {willisapi_client-1.9.3 → willisapi_client-1.9.4}/willisapi_client.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: willisapi_client
3
- Version: 1.9.3
3
+ Version: 1.9.4
4
4
  Summary: A Python client for willisapi
5
5
  Home-page: https://github.com/bklynhlth/willsiapi_client
6
6
  Author: bklynhlth
@@ -95,11 +95,10 @@ summary = willisapi.upload(key, 'data.csv', force_uploade=True)
95
95
 
96
96
  Before uploading, the client validates your CSV for:
97
97
 
98
- - **Required Columns**: Must include study_id, site_id, rater_email, participant_id, visit_name, visit_order, coa_name, coa_item_number, coa_item_value, file_path, time_collected.
99
- - **Optional Columns**: age, sex, race, language.
98
+ - **Required Columns**: Must include study_id, site_id, participant_id, visit_name, visit_order, coa_name, coa_item_number, coa_item_value, file_path, time_collected.
99
+ - **Optional Columns**: rater_id, age, sex, race, language.
100
100
  - **Valid COA Name**: Only these values are allowed: MADRS, YMRS, PHQ-9, GAD-7.
101
101
  - **Valid Audio File**: The file path in each row must exist and be accessible.
102
- - **Valid Email**: rater_email must be a valid email.
103
102
  - **Valid Data Types**: visit_order, age, coa_item_number, and coa_item_value must be numeric.
104
103
  - **Language**: If present, must be in the allowed language choices.
105
104
 
@@ -107,6 +106,20 @@ If any validation fails, errors are collected and returned for review before upl
107
106
 
108
107
  ---
109
108
 
109
+ **Processed Data Upload**
110
+
111
+ To upload a processed data CSV file:
112
+
113
+ ```python
114
+ summary = willisapi.processed_upload(key, '/path/to/processed-data.csv')
115
+ ```
116
+
117
+ How to Call the Function
118
+ - key: Your PAT token.
119
+ - processed-data.csv: Path to your processed data CSV file.
120
+
121
+ ---
122
+
110
123
  For more information on how to organize the `data.csv`, visit the [Github Wiki](http://www.github.com/bklynhlth/willisapi_client/wiki).
111
124
 
112
125
 
@@ -83,11 +83,10 @@ summary = willisapi.upload(key, 'data.csv', force_uploade=True)
83
83
 
84
84
  Before uploading, the client validates your CSV for:
85
85
 
86
- - **Required Columns**: Must include study_id, site_id, rater_email, participant_id, visit_name, visit_order, coa_name, coa_item_number, coa_item_value, file_path, time_collected.
87
- - **Optional Columns**: age, sex, race, language.
86
+ - **Required Columns**: Must include study_id, site_id, participant_id, visit_name, visit_order, coa_name, coa_item_number, coa_item_value, file_path, time_collected.
87
+ - **Optional Columns**: rater_id, age, sex, race, language.
88
88
  - **Valid COA Name**: Only these values are allowed: MADRS, YMRS, PHQ-9, GAD-7.
89
89
  - **Valid Audio File**: The file path in each row must exist and be accessible.
90
- - **Valid Email**: rater_email must be a valid email.
91
90
  - **Valid Data Types**: visit_order, age, coa_item_number, and coa_item_value must be numeric.
92
91
  - **Language**: If present, must be in the allowed language choices.
93
92
 
@@ -95,6 +94,20 @@ If any validation fails, errors are collected and returned for review before upl
95
94
 
96
95
  ---
97
96
 
97
+ **Processed Data Upload**
98
+
99
+ To upload a processed data CSV file:
100
+
101
+ ```python
102
+ summary = willisapi.processed_upload(key, '/path/to/processed-data.csv')
103
+ ```
104
+
105
+ How to Call the Function
106
+ - key: Your PAT token.
107
+ - processed-data.csv: Path to your processed data CSV file.
108
+
109
+ ---
110
+
98
111
  For more information on how to organize the `data.csv`, visit the [Github Wiki](http://www.github.com/bklynhlth/willisapi_client/wiki).
99
112
 
100
113
 
@@ -5,10 +5,12 @@ from willisapi_client.services.api import (
5
5
  willis_diarize_call_remaining,
6
6
  willis_diarize,
7
7
  upload,
8
+ processed_upload
8
9
  )
9
10
 
10
11
  __all__ = [
11
12
  "willis_diarize_call_remaining",
12
13
  "willis_diarize",
13
14
  "upload",
15
+ "processed_upload"
14
16
  ]
@@ -1,7 +1,7 @@
1
1
  """Version details for willisapi_client"""
2
2
 
3
3
  __client__ = "willisapi_client"
4
- __latestVersion__ = "1.9.3"
4
+ __latestVersion__ = "1.9.4"
5
5
  __url__ = "https://github.com/bklynhlth/willsiapi_client"
6
6
  __short_description__ = "A Python client for willisapi"
7
7
  __content_type__ = "text/markdown"
@@ -5,4 +5,4 @@ from willisapi_client.services.diarize import (
5
5
  willis_diarize_call_remaining,
6
6
  willis_diarize,
7
7
  )
8
- from willisapi_client.services.metadata import upload
8
+ from willisapi_client.services.metadata import upload, processed_upload
@@ -0,0 +1,4 @@
1
+ # website: https://www.brooklyn.health
2
+ from .upload import upload, processed_upload
3
+
4
+ __all__ = ["upload", "processed_upload"]
@@ -0,0 +1,194 @@
1
+ import pandas as pd
2
+ from tqdm import tqdm
3
+ from datetime import datetime
4
+ import requests
5
+ import mimetypes
6
+ import os
7
+
8
+ from willisapi_client.timer import measure
9
+ from willisapi_client.willisapi_client import WillisapiClient
10
+ from willisapi_client.logging_setup import logger as logger
11
+ from willisapi_client.services.metadata.utils import (
12
+ MetadataValidation,
13
+ ProcessedMetadataValidation,
14
+ UploadUtils,
15
+ find_files_with_pattern,
16
+ get_last_n_directories,
17
+ )
18
+
19
+
20
+ @measure
21
+ def upload(api_key: str, csv_path: str, **kwargs):
22
+
23
+ force_upload = kwargs.get("force_upload", False)
24
+ csv = MetadataValidation(csv_path=csv_path, force_upload=force_upload)
25
+ if csv.load_and_validate():
26
+ logger.info(f'{datetime.now().strftime("%H:%M:%S")}: csv check passed')
27
+ csv.create_final_csv()
28
+
29
+ wc = WillisapiClient(env=kwargs.get("env"))
30
+ url = wc.get_upload_url()
31
+ headers = wc.get_headers()
32
+ headers["Authorization"] = f"token {api_key}"
33
+ logger.info(f'{datetime.now().strftime("%H:%M:%S")}: beginning upload')
34
+
35
+ results = []
36
+ for index, row in tqdm(
37
+ csv.transformed_df.iterrows(), total=csv.transformed_df.shape[0]
38
+ ):
39
+ u = UploadUtils(row)
40
+ valid, err = u.validate_row()
41
+ result_row = row.to_dict()
42
+ if valid:
43
+ payload = u.generate_payload()
44
+ res = u.post(api_key, url, headers, payload)
45
+ if res.get("upload_status") == "Success":
46
+ result_row["upload_status"] = "Success"
47
+ result_row["error"] = None
48
+
49
+ # Handle S3 upload if presigned URL is provided
50
+ presigned = res.get("response", {}).get("presigned")
51
+ if presigned:
52
+ try:
53
+ content_type, _ = mimetypes.guess_type(
54
+ payload.get("filename")
55
+ )
56
+ if not content_type:
57
+ content_type = "application/octet-stream"
58
+ with open(row.file_path, "rb") as f:
59
+ response = requests.put(
60
+ presigned,
61
+ data=f,
62
+ headers={
63
+ "x-amz-checksum-sha256": payload.get(
64
+ "checksum"
65
+ ),
66
+ "x-amz-sdk-checksum-algorithm": "SHA256",
67
+ "Content-Type": content_type,
68
+ },
69
+ )
70
+ if response.status_code == 200:
71
+ result_row["upload_status"] = "Success"
72
+ else:
73
+ result_row["upload_status"] = "Failed"
74
+ result_row["error"] = (
75
+ f"S3 upload failed with status code {response.status_code}"
76
+ )
77
+ except Exception as ex:
78
+ result_row["upload_status"] = "Failed"
79
+ result_row["error"] = str(ex)
80
+ else:
81
+ result_row["upload_status"] = "Failed"
82
+ result_row["error"] = (
83
+ "Collect recording upload URL not received"
84
+ )
85
+ else:
86
+ result_row["upload_status"] = "Failed"
87
+ result_row["error"] = res.get("error")
88
+ else:
89
+ result_row["upload_status"] = "Failed"
90
+ result_row["error"] = f"{err}"
91
+ results.append(result_row)
92
+
93
+ results_df = pd.DataFrame(results)
94
+ return results_df
95
+ else:
96
+ logger.error(f'{datetime.now().strftime("%H:%M:%S")}: csv check failed')
97
+ logger.error(csv.errors)
98
+ return None
99
+
100
+
101
+ @measure
102
+ def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
103
+
104
+ force_upload = kwargs.get("force_upload", False)
105
+ csv = ProcessedMetadataValidation(csv_path=csv_path, force_upload=force_upload)
106
+ if csv.load_and_validate():
107
+ logger.info(f'{datetime.now().strftime("%H:%M:%S")}: csv check passed')
108
+ csv.create_final_csv()
109
+
110
+ wc = WillisapiClient(env=kwargs.get("env"))
111
+ url = wc.get_processed_upload_url()
112
+ headers = wc.get_headers()
113
+ headers["Authorization"] = f"token {api_key}"
114
+ logger.info(f'{datetime.now().strftime("%H:%M:%S")}: beginning upload')
115
+
116
+ results = []
117
+ for index, row in tqdm(
118
+ csv.transformed_df.iterrows(), total=csv.transformed_df.shape[0]
119
+ ):
120
+ u = UploadUtils(row)
121
+ valid, err = u.validate_processed_data_row()
122
+ result_row = row.to_dict()
123
+ if valid:
124
+ filename = os.path.basename(row.recording).split(".")[0]
125
+ files = []
126
+ for file in find_files_with_pattern(output_path, filename):
127
+ key, error = get_last_n_directories(file, n=2)
128
+ if error:
129
+ continue
130
+ checksum = u.calculate_file_checksum(file)
131
+ files.append(
132
+ {
133
+ "index": index,
134
+ "recording": file,
135
+ "key": key,
136
+ "checksum": checksum,
137
+ }
138
+ )
139
+ payload = u.generate_processed_payload(files)
140
+ res = u.post(api_key, url, headers, payload)
141
+ if res.get("upload_status") == "Success":
142
+ result_row["upload_status"] = "Success"
143
+ result_row["error"] = None
144
+
145
+ # Handle S3 upload if presigned URL is provided
146
+ for file_presigned in res.get("response", []):
147
+ presigned = file_presigned.get("presigned")
148
+ checksum = file_presigned.get("checksum")
149
+ recording = file_presigned.get("recording")
150
+ index = file_presigned.get("index")
151
+ try:
152
+ content_type, _ = mimetypes.guess_type(recording)
153
+ if not content_type:
154
+ content_type = "text/csv"
155
+ with open(recording, "rb") as f:
156
+ response = requests.put(
157
+ presigned,
158
+ data=f,
159
+ headers={
160
+ "x-amz-checksum-sha256": checksum,
161
+ "x-amz-sdk-checksum-algorithm": "SHA256",
162
+ "Content-Type": content_type,
163
+ },
164
+ )
165
+ if response.status_code == 200:
166
+ result_row["upload_status"] = "Success"
167
+ else:
168
+ result_row["upload_status"] = "Failed"
169
+ if result_row["error"] is None:
170
+ result_row["error"] = ""
171
+ result_row["error"] = (
172
+ result_row["error"]
173
+ + "\n"
174
+ + f"S3 upload failed with status code {response.status_code} for file {recording}"
175
+ )
176
+ except Exception as ex:
177
+ result_row["upload_status"] = "Failed"
178
+ if result_row["error"] is None:
179
+ result_row["error"] = ""
180
+ result_row["error"] = result_row["error"] + "\n" + str(ex)
181
+ else:
182
+ result_row["upload_status"] = "Failed"
183
+ result_row["error"] = res.get("error")
184
+ else:
185
+ result_row["upload_status"] = "Failed"
186
+ result_row["error"] = f"{err}"
187
+ results.append(result_row)
188
+
189
+ results_df = pd.DataFrame(results)
190
+ return results_df
191
+ else:
192
+ logger.error(f'{datetime.now().strftime("%H:%M:%S")}: csv check failed')
193
+ logger.error(csv.errors)
194
+ return None