willisapi-client 1.9.4__tar.gz → 1.9.5__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.5}/PKG-INFO +1 -1
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/__version__.py +1 -1
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/metadata/upload.py +44 -29
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/metadata/utils.py +45 -7
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client.egg-info/PKG-INFO +1 -1
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/LICENSE +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/MANIFEST.in +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/README.md +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/RELEASE.md +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/requirements.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/setup.cfg +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/setup.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/tests/test_diarize.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/tests/test_diarize_call_remaining.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/logging_setup.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/api.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/diarize/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/diarize/diarize_utils.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/diarize/willisdiarize.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/diarize/willisdiarize_call_remaining.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/exceptions.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/metadata/__init__.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/metadata/language_choices.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/timer.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/willisapi_client.py +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client.egg-info/SOURCES.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client.egg-info/dependency_links.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client.egg-info/not-zip-safe +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client.egg-info/requires.txt +0 -0
- {willisapi_client-1.9.4 → willisapi_client-1.9.5}/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.5"
|
|
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.5}/willisapi_client/services/metadata/upload.py
RENAMED
|
@@ -16,6 +16,8 @@ from willisapi_client.services.metadata.utils import (
|
|
|
16
16
|
get_last_n_directories,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
VALID_SCORE_TYPES = ["rater", "reviewer"]
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
@measure
|
|
21
23
|
def upload(api_key: str, csv_path: str, **kwargs):
|
|
@@ -101,8 +103,17 @@ def upload(api_key: str, csv_path: str, **kwargs):
|
|
|
101
103
|
@measure
|
|
102
104
|
def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
103
105
|
|
|
106
|
+
score_type = kwargs.get("score_type", "rater")
|
|
107
|
+
if score_type not in VALID_SCORE_TYPES:
|
|
108
|
+
logger.error(
|
|
109
|
+
f"Invalid score_type '{score_type}'. Allowed values: {', '.join(VALID_SCORE_TYPES)}"
|
|
110
|
+
)
|
|
111
|
+
return None
|
|
112
|
+
|
|
104
113
|
force_upload = kwargs.get("force_upload", False)
|
|
105
|
-
csv = ProcessedMetadataValidation(
|
|
114
|
+
csv = ProcessedMetadataValidation(
|
|
115
|
+
csv_path=csv_path, force_upload=force_upload, score_type=score_type
|
|
116
|
+
)
|
|
106
117
|
if csv.load_and_validate():
|
|
107
118
|
logger.info(f'{datetime.now().strftime("%H:%M:%S")}: csv check passed')
|
|
108
119
|
csv.create_final_csv()
|
|
@@ -121,28 +132,39 @@ def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
|
121
132
|
valid, err = u.validate_processed_data_row()
|
|
122
133
|
result_row = row.to_dict()
|
|
123
134
|
if valid:
|
|
124
|
-
filename = os.path.basename(row.recording).split(".")[0]
|
|
125
135
|
files = []
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
{
|
|
133
|
-
"index": index,
|
|
134
|
-
"recording": file,
|
|
135
|
-
"key": key,
|
|
136
|
-
"checksum": checksum,
|
|
137
|
-
}
|
|
136
|
+
if score_type != "reviewer":
|
|
137
|
+
recording_val = getattr(row, "recording", None)
|
|
138
|
+
filename = (
|
|
139
|
+
os.path.basename(recording_val).split(".")[0]
|
|
140
|
+
if recording_val
|
|
141
|
+
else None
|
|
138
142
|
)
|
|
139
|
-
|
|
143
|
+
for file in (
|
|
144
|
+
find_files_with_pattern(output_path, filename)
|
|
145
|
+
if filename
|
|
146
|
+
else []
|
|
147
|
+
):
|
|
148
|
+
key, error = get_last_n_directories(file, n=2)
|
|
149
|
+
if error:
|
|
150
|
+
continue
|
|
151
|
+
checksum = u.calculate_file_checksum(file)
|
|
152
|
+
files.append(
|
|
153
|
+
{
|
|
154
|
+
"index": index,
|
|
155
|
+
"recording": file,
|
|
156
|
+
"key": key,
|
|
157
|
+
"checksum": checksum,
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
payload = u.generate_processed_payload(files, score_type=score_type)
|
|
140
161
|
res = u.post(api_key, url, headers, payload)
|
|
141
162
|
if res.get("upload_status") == "Success":
|
|
142
163
|
result_row["upload_status"] = "Success"
|
|
143
164
|
result_row["error"] = None
|
|
144
165
|
|
|
145
166
|
# Handle S3 upload if presigned URL is provided
|
|
167
|
+
s3_errors = []
|
|
146
168
|
for file_presigned in res.get("response", []):
|
|
147
169
|
presigned = file_presigned.get("presigned")
|
|
148
170
|
checksum = file_presigned.get("checksum")
|
|
@@ -162,22 +184,15 @@ def processed_upload(api_key: str, csv_path: str, output_path: str, **kwargs):
|
|
|
162
184
|
"Content-Type": content_type,
|
|
163
185
|
},
|
|
164
186
|
)
|
|
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}"
|
|
187
|
+
if response.status_code != 200:
|
|
188
|
+
s3_errors.append(
|
|
189
|
+
f"S3 upload failed with status code {response.status_code} for file {recording}"
|
|
175
190
|
)
|
|
176
191
|
except Exception as ex:
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
s3_errors.append(str(ex))
|
|
193
|
+
if s3_errors:
|
|
194
|
+
result_row["upload_status"] = "Failed"
|
|
195
|
+
result_row["error"] = "\n".join(s3_errors)
|
|
181
196
|
else:
|
|
182
197
|
result_row["upload_status"] = "Failed"
|
|
183
198
|
result_row["error"] = res.get("error")
|
{willisapi_client-1.9.4 → willisapi_client-1.9.5}/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.5}/willisapi_client/services/diarize/__init__.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.5}/willisapi_client/services/diarize/diarize_utils.py
RENAMED
|
File without changes
|
{willisapi_client-1.9.4 → willisapi_client-1.9.5}/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.5}/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.5}/willisapi_client.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|