willisapi-client 1.9.4__tar.gz → 1.9.6__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.
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/PKG-INFO +1 -1
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/__version__.py +1 -1
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/upload.py +48 -29
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/utils.py +45 -7
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/PKG-INFO +1 -1
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/LICENSE +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/MANIFEST.in +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/README.md +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/RELEASE.md +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/requirements.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/setup.cfg +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/setup.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/tests/test_diarize.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/tests/test_diarize_call_remaining.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/logging_setup.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/api.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/diarize_utils.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/willisdiarize.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/willisdiarize_call_remaining.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/exceptions.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/language_choices.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/timer.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/willisapi_client.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/SOURCES.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/dependency_links.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/not-zip-safe +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/requires.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Version details for willisapi_client"""
|
|
2
2
|
|
|
3
3
|
__client__ = "willisapi_client"
|
|
4
|
-
__latestVersion__ = "1.9.
|
|
4
|
+
__latestVersion__ = "1.9.6"
|
|
5
5
|
__url__ = "https://github.com/bklynhlth/willsiapi_client"
|
|
6
6
|
__short_description__ = "A Python client for willisapi"
|
|
7
7
|
__content_type__ = "text/markdown"
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/upload.py
RENAMED
|
@@ -5,6 +5,10 @@ import requests
|
|
|
5
5
|
import mimetypes
|
|
6
6
|
import os
|
|
7
7
|
|
|
8
|
+
# Register extensions that Python's default mimetypes table doesn't know about.
|
|
9
|
+
# The server signs presigned URLs with these types, so the client must match.
|
|
10
|
+
mimetypes.add_type("application/vnd.apache.parquet", ".parquet")
|
|
11
|
+
|
|
8
12
|
from willisapi_client.timer import measure
|
|
9
13
|
from willisapi_client.willisapi_client import WillisapiClient
|
|
10
14
|
from willisapi_client.logging_setup import logger as logger
|
|
@@ -16,6 +20,8 @@ from willisapi_client.services.metadata.utils import (
|
|
|
16
20
|
get_last_n_directories,
|
|
17
21
|
)
|
|
18
22
|
|
|
23
|
+
VALID_SCORE_TYPES = ["rater", "reviewer"]
|
|
24
|
+
|
|
19
25
|
|
|
20
26
|
@measure
|
|
21
27
|
def upload(api_key: str, csv_path: str, **kwargs):
|
|
@@ -101,8 +107,17 @@ def upload(api_key: str, csv_path: str, **kwargs):
|
|
|
101
107
|
@measure
|
|
102
108
|
def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
103
109
|
|
|
110
|
+
score_type = kwargs.get("score_type", "rater")
|
|
111
|
+
if score_type not in VALID_SCORE_TYPES:
|
|
112
|
+
logger.error(
|
|
113
|
+
f"Invalid score_type '{score_type}'. Allowed values: {', '.join(VALID_SCORE_TYPES)}"
|
|
114
|
+
)
|
|
115
|
+
return None
|
|
116
|
+
|
|
104
117
|
force_upload = kwargs.get("force_upload", False)
|
|
105
|
-
csv = ProcessedMetadataValidation(
|
|
118
|
+
csv = ProcessedMetadataValidation(
|
|
119
|
+
csv_path=csv_path, force_upload=force_upload, score_type=score_type
|
|
120
|
+
)
|
|
106
121
|
if csv.load_and_validate():
|
|
107
122
|
logger.info(f'{datetime.now().strftime("%H:%M:%S")}: csv check passed')
|
|
108
123
|
csv.create_final_csv()
|
|
@@ -121,28 +136,39 @@ def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
|
121
136
|
valid, err = u.validate_processed_data_row()
|
|
122
137
|
result_row = row.to_dict()
|
|
123
138
|
if valid:
|
|
124
|
-
filename = os.path.basename(row.recording).split(".")[0]
|
|
125
139
|
files = []
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
"index": index,
|
|
134
|
-
"recording": file,
|
|
135
|
-
"key": key,
|
|
136
|
-
"checksum": checksum,
|
|
137
|
-
}
|
|
140
|
+
if score_type != "reviewer":
|
|
141
|
+
recording_val = getattr(row, "recording", None)
|
|
142
|
+
filename = (
|
|
143
|
+
os.path.basename(recording_val).split(".")[0]
|
|
144
|
+
if recording_val
|
|
145
|
+
else None
|
|
138
146
|
)
|
|
139
|
-
|
|
147
|
+
for file in (
|
|
148
|
+
find_files_with_pattern(output_path, filename)
|
|
149
|
+
if filename
|
|
150
|
+
else []
|
|
151
|
+
):
|
|
152
|
+
key, error = get_last_n_directories(file, n=2)
|
|
153
|
+
if error:
|
|
154
|
+
continue
|
|
155
|
+
checksum = u.calculate_file_checksum(file)
|
|
156
|
+
files.append(
|
|
157
|
+
{
|
|
158
|
+
"index": index,
|
|
159
|
+
"recording": file,
|
|
160
|
+
"key": key,
|
|
161
|
+
"checksum": checksum,
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
payload = u.generate_processed_payload(files, score_type=score_type)
|
|
140
165
|
res = u.post(api_key, url, headers, payload)
|
|
141
166
|
if res.get("upload_status") == "Success":
|
|
142
167
|
result_row["upload_status"] = "Success"
|
|
143
168
|
result_row["error"] = None
|
|
144
169
|
|
|
145
170
|
# Handle S3 upload if presigned URL is provided
|
|
171
|
+
s3_errors = []
|
|
146
172
|
for file_presigned in res.get("response", []):
|
|
147
173
|
presigned = file_presigned.get("presigned")
|
|
148
174
|
checksum = file_presigned.get("checksum")
|
|
@@ -162,22 +188,15 @@ def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
|
162
188
|
"Content-Type": content_type,
|
|
163
189
|
},
|
|
164
190
|
)
|
|
165
|
-
if response.status_code
|
|
166
|
-
|
|
167
|
-
|
|
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}"
|
|
191
|
+
if response.status_code != 200:
|
|
192
|
+
s3_errors.append(
|
|
193
|
+
f"S3 upload failed with status code {response.status_code} for file {recording}"
|
|
175
194
|
)
|
|
176
195
|
except Exception as ex:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
196
|
+
s3_errors.append(str(ex))
|
|
197
|
+
if s3_errors:
|
|
198
|
+
result_row["upload_status"] = "Failed"
|
|
199
|
+
result_row["error"] = "\n".join(s3_errors)
|
|
181
200
|
else:
|
|
182
201
|
result_row["upload_status"] = "Failed"
|
|
183
202
|
result_row["error"] = res.get("error")
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/utils.py
RENAMED
|
@@ -12,9 +12,16 @@ from .language_choices import (
|
|
|
12
12
|
)
|
|
13
13
|
from dateutil import parser
|
|
14
14
|
|
|
15
|
-
ALLOWED_COA_NAMES = ["MADRS", "YMRS", "PHQ-9", "GAD-7", "HAM-D17"]
|
|
15
|
+
ALLOWED_COA_NAMES = ["MADRS", "YMRS", "PHQ-9", "GAD-7", "HAM-D17", "HAMD17"]
|
|
16
16
|
|
|
17
|
-
COA_ITEM_COUNTS = {
|
|
17
|
+
COA_ITEM_COUNTS = {
|
|
18
|
+
"MADRS": 10,
|
|
19
|
+
"YMRS": 10,
|
|
20
|
+
"PHQ-9": 9,
|
|
21
|
+
"GAD-7": 7,
|
|
22
|
+
"HAM-D17": 17,
|
|
23
|
+
"HAMD17": 17,
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
|
|
20
27
|
class MetadataValidation:
|
|
@@ -453,11 +460,14 @@ class UploadUtils:
|
|
|
453
460
|
"checksum": self.calculate_file_checksum(self.row.file_path),
|
|
454
461
|
"recording_order": int(self.row.recording_order),
|
|
455
462
|
"is_last_recording": self.row.is_last_recording,
|
|
456
|
-
"
|
|
463
|
+
# "time_collected": parser.parse(self.row.time_collected).isoformat(),
|
|
457
464
|
}
|
|
458
465
|
return payload
|
|
459
466
|
|
|
460
|
-
def generate_processed_payload(
|
|
467
|
+
def generate_processed_payload(
|
|
468
|
+
self, files: List[Dict[str, str]], score_type: str = "rater"
|
|
469
|
+
) -> Dict[str, Any]:
|
|
470
|
+
recording_val = getattr(self.row, "recording", None)
|
|
461
471
|
payload = {
|
|
462
472
|
"study_id": self.row.study_id,
|
|
463
473
|
"site_id": self.row.site_id,
|
|
@@ -467,12 +477,16 @@ class UploadUtils:
|
|
|
467
477
|
"visit_id": self.row.visit_id,
|
|
468
478
|
"visit_order": int(self.row.visit_order),
|
|
469
479
|
"coa_id": self.row.coa_id,
|
|
470
|
-
"filename": os.path.basename(
|
|
480
|
+
"filename": os.path.basename(recording_val) if recording_val else "",
|
|
471
481
|
"actual_scores": json.loads(self.row.scores_actual),
|
|
472
482
|
"files": files,
|
|
473
483
|
"force_upload": self.row.force_upload,
|
|
474
484
|
"timestamp": parser.parse(self.row.timestamp).isoformat(),
|
|
485
|
+
"score_type": score_type,
|
|
475
486
|
}
|
|
487
|
+
site_country = getattr(self.row, "site_country", None)
|
|
488
|
+
if site_country:
|
|
489
|
+
payload["site_country"] = site_country
|
|
476
490
|
return payload
|
|
477
491
|
|
|
478
492
|
def post(
|
|
@@ -500,17 +514,17 @@ class ProcessedMetadataValidation:
|
|
|
500
514
|
"visit_order",
|
|
501
515
|
"coa_id",
|
|
502
516
|
"timestamp",
|
|
503
|
-
"recording",
|
|
504
517
|
"recording_order",
|
|
505
518
|
"workflow",
|
|
506
519
|
]
|
|
507
520
|
|
|
508
|
-
OPTIONAL_COLUMNS = ["rater_id", "language"]
|
|
521
|
+
OPTIONAL_COLUMNS = ["rater_id", "language", "site_country", "age", "sex", "race"]
|
|
509
522
|
|
|
510
523
|
def __init__(
|
|
511
524
|
self,
|
|
512
525
|
csv_path: str,
|
|
513
526
|
force_upload: bool = False,
|
|
527
|
+
score_type: str = "rater",
|
|
514
528
|
):
|
|
515
529
|
"""
|
|
516
530
|
Initialize validator with CSV file path.
|
|
@@ -523,6 +537,7 @@ class ProcessedMetadataValidation:
|
|
|
523
537
|
self.errors = []
|
|
524
538
|
self.transformed_df = None
|
|
525
539
|
self.force_upload = force_upload
|
|
540
|
+
self.score_type = score_type
|
|
526
541
|
|
|
527
542
|
def validate_columns(self) -> bool:
|
|
528
543
|
"""
|
|
@@ -540,6 +555,28 @@ class ProcessedMetadataValidation:
|
|
|
540
555
|
return False
|
|
541
556
|
return True
|
|
542
557
|
|
|
558
|
+
def validate_recording_field(self) -> bool:
|
|
559
|
+
"""
|
|
560
|
+
Validate the recording column based on score_type.
|
|
561
|
+
- rater: recording column must be present and non-blank for every row.
|
|
562
|
+
- reviewer: recording column may be absent or blank.
|
|
563
|
+
"""
|
|
564
|
+
if self.score_type == "rater":
|
|
565
|
+
if "recording" not in self.df.columns:
|
|
566
|
+
self.errors.append(
|
|
567
|
+
"Missing required column: recording (required when score_type='rater')"
|
|
568
|
+
)
|
|
569
|
+
return False
|
|
570
|
+
blank_mask = self.df["recording"].isna() | (
|
|
571
|
+
self.df["recording"].astype(str).str.strip() == ""
|
|
572
|
+
)
|
|
573
|
+
if blank_mask.any():
|
|
574
|
+
self.errors.append(
|
|
575
|
+
"recording must not be blank when score_type='rater'"
|
|
576
|
+
)
|
|
577
|
+
return False
|
|
578
|
+
return True
|
|
579
|
+
|
|
543
580
|
def validate_data_types(self) -> bool:
|
|
544
581
|
"""
|
|
545
582
|
Validate data types for key columns.
|
|
@@ -607,6 +644,7 @@ class ProcessedMetadataValidation:
|
|
|
607
644
|
|
|
608
645
|
validations = [
|
|
609
646
|
self.validate_columns(),
|
|
647
|
+
self.validate_recording_field(),
|
|
610
648
|
self.validate_data_types(),
|
|
611
649
|
self.validate_coa_names(),
|
|
612
650
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/__init__.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/diarize_utils.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/diarize/willisdiarize.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client/services/metadata/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.6}/willisapi_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|