@@ -257,7 +257,9 @@ def _participants(n_participants: int, *, prefix: str) -> tuple[str, ...]:
257257 if not isinstance (prefix , str ) or not prefix :
258258 raise ValueError ("'participant_prefix' must be a non-empty string." )
259259 width = max (2 , len (str (n_participants )))
260- return tuple (f"{ prefix } { number :0{width }d} " for number in range (1 , n_participants + 1 ))
260+ return tuple (
261+ f"{ prefix } { number :0{width }d} " for number in range (1 , n_participants + 1 )
262+ )
261263
262264
263265def _config_value (
@@ -312,16 +314,16 @@ def _parse_study_config(
312314 "study_name" ,
313315 )
314316 participant_prefix = _as_non_empty_string (
315- _config_value (
316- config , "participant_prefix" , "participantPrefix" , default = "VP_"
317- ),
317+ _config_value (config , "participant_prefix" , "participantPrefix" , default = "VP_" ),
318318 "participant_prefix" ,
319319 )
320320 sample_prefix = _as_non_empty_string (
321321 _config_value (config , "sample_prefix" , "samplePrefix" , default = "S" ),
322322 "sample_prefix" ,
323323 )
324- start_sample = _config_value (config , "start_sample_from_zero" , "startSampleFromZero" )
324+ start_sample = _config_value (
325+ config , "start_sample_from_zero" , "startSampleFromZero"
326+ )
325327 qr_start_sample = _config_value (config , "SS" )
326328 if qr_start_sample is not None :
327329 qr_prefix , qr_zero = _parse_qr_start_sample (qr_start_sample )
@@ -363,16 +365,24 @@ def _parse_study_config(
363365 )
364366 has_evening_sample = _as_bool (
365367 _config_value (
366- config , "has_evening_sample" , "hasEveningSample" , "E" , default = bool (absolute_times )
368+ config ,
369+ "has_evening_sample" ,
370+ "hasEveningSample" ,
371+ "E" ,
372+ default = bool (absolute_times ),
367373 ),
368374 "has_evening_sample" ,
369375 )
370376 check_duplicates = _as_bool (
371- _config_value (config , "check_duplicates" , "checkDuplicates" , "FD" , default = False ),
377+ _config_value (
378+ config , "check_duplicates" , "checkDuplicates" , "FD" , default = False
379+ ),
372380 "check_duplicates" ,
373381 )
374382 enable_manual_scan = _as_bool (
375- _config_value (config , "enable_manual_scan" , "enableManualScan" , "FM" , default = False ),
383+ _config_value (
384+ config , "enable_manual_scan" , "enableManualScan" , "FM" , default = False
385+ ),
376386 "enable_manual_scan" ,
377387 )
378388 configured_participants = _config_value (
@@ -455,7 +465,11 @@ def _decode_qr_config(payload: str) -> dict[str, Any]:
455465 except ValueError as error :
456466 raise ValueError (f"QR field { key !r} must be 0 or 1." ) from error
457467 if "T" in decoded :
458- decoded ["T" ] = [] if not decoded ["T" ] else [int (value ) for value in decoded ["T" ].split ("," )]
468+ decoded ["T" ] = (
469+ []
470+ if not decoded ["T" ]
471+ else [int (value ) for value in decoded ["T" ].split ("," )]
472+ )
459473 if "A" in decoded :
460474 decoded ["A" ] = [] if not decoded ["A" ] else decoded ["A" ].split ("," )
461475 return decoded
@@ -475,7 +489,9 @@ def _parse_registrations(
475489 "registration_date" : "2026-01-04" ,
476490 }
477491 ]
478- if isinstance (registrations , (str , bytes )) or not isinstance (registrations , Sequence ):
492+ if isinstance (registrations , (str , bytes )) or not isinstance (
493+ registrations , Sequence
494+ ):
479495 raise TypeError ("'registrations' must be a sequence of mappings." )
480496 if not registrations :
481497 raise ValueError ("'registrations' must contain at least one registration." )
@@ -509,18 +525,32 @@ def _parse_registrations(
509525 _config_value (
510526 raw_registration ,
511527 "registration_date" ,
512- default = (pd .Timestamp ("2026-01-04" ) + pd .Timedelta (days = 7 * (number - 1 ))).date ().isoformat (),
528+ default = (
529+ pd .Timestamp ("2026-01-04" ) + pd .Timedelta (days = 7 * (number - 1 ))
530+ )
531+ .date ()
532+ .isoformat (),
513533 ),
514534 f"registrations[{ number - 1 } ].registration_date" ,
515535 )
516536 raw_dates = _config_value (raw_registration , "collection_dates" , default = None )
517537 if raw_dates is None :
518538 start = pd .Timestamp (date ) + pd .Timedelta (days = 1 )
519- dates = tuple ((start + pd .Timedelta (days = day )).date ().isoformat () for day in range (study_days ))
539+ dates = tuple (
540+ (start + pd .Timedelta (days = day )).date ().isoformat ()
541+ for day in range (study_days )
542+ )
520543 else :
521- if isinstance (raw_dates , (str , bytes )) or not isinstance (raw_dates , Sequence ):
522- raise TypeError (f"registrations[{ number - 1 } ].collection_dates must be a sequence." )
523- dates = tuple (_as_date (value , f"registrations[{ number - 1 } ].collection_dates" ) for value in raw_dates )
544+ if isinstance (raw_dates , (str , bytes )) or not isinstance (
545+ raw_dates , Sequence
546+ ):
547+ raise TypeError (
548+ f"registrations[{ number - 1 } ].collection_dates must be a sequence."
549+ )
550+ dates = tuple (
551+ _as_date (value , f"registrations[{ number - 1 } ].collection_dates" )
552+ for value in raw_dates
553+ )
524554 if len (dates ) != study_days :
525555 raise ValueError (
526556 f"registrations[{ number - 1 } ].collection_dates has { len (dates )} dates, "
@@ -531,18 +561,28 @@ def _parse_registrations(
531561 saliva_ids = None
532562 else :
533563 if isinstance (raw_ids , (str , bytes )) or not isinstance (raw_ids , Sequence ):
534- raise TypeError (f"registrations[{ number - 1 } ].saliva_ids must be a sequence." )
535- saliva_ids = tuple (_as_non_empty_string (value , "saliva_ids" ) for value in raw_ids )
536- if len (saliva_ids ) != sample_count or len (set (saliva_ids )) != len (saliva_ids ):
564+ raise TypeError (
565+ f"registrations[{ number - 1 } ].saliva_ids must be a sequence."
566+ )
567+ saliva_ids = tuple (
568+ _as_non_empty_string (value , "saliva_ids" ) for value in raw_ids
569+ )
570+ if len (saliva_ids ) != sample_count or len (set (saliva_ids )) != len (
571+ saliva_ids
572+ ):
537573 raise ValueError (
538574 f"registrations[{ number - 1 } ].saliva_ids must contain { sample_count } unique identifiers."
539575 )
540576 condition = _config_value (raw_registration , "condition" , default = None )
541577 if condition is not None :
542578 condition = _as_non_empty_string (condition , "condition" )
543- parsed .append (_Registration (name , study_days , date , dates , saliva_ids , condition ))
579+ parsed .append (
580+ _Registration (name , study_days , date , dates , saliva_ids , condition )
581+ )
544582 if len ({registration .study_name for registration in parsed }) != len (parsed ):
545- raise ValueError ("Registration study_name values must be unique within a synthetic study." )
583+ raise ValueError (
584+ "Registration study_name values must be unique within a synthetic study."
585+ )
546586 return tuple (parsed )
547587
548588
@@ -644,7 +684,9 @@ def _as_absolute_schedule(value: Any) -> tuple[str, ...]:
644684 if isinstance (item , (int , np .integer )):
645685 item = f"{ int (item ):04d} "
646686 if not isinstance (item , str ):
647- raise TypeError ("'saliva_alarm_times' entries must be strings or HHMM integers." )
687+ raise TypeError (
688+ "'saliva_alarm_times' entries must be strings or HHMM integers."
689+ )
648690 compact = item .replace (":" , "" )
649691 if len (compact ) != 4 or not compact .isdigit ():
650692 raise ValueError (f"Invalid fixed saliva alarm time: { item !r} ." )
@@ -681,7 +723,12 @@ def _parse_qr_start_sample(value: Any) -> tuple[str, bool]:
681723
682724def _filename_token (value : Any ) -> str :
683725 text = _as_non_empty_string (value , "filename_token" )
684- return "" .join (character if character .isalnum () else "-" for character in text ).strip ("-" ) or "study"
726+ return (
727+ "" .join (character if character .isalnum () else "-" for character in text ).strip (
728+ "-"
729+ )
730+ or "study"
731+ )
685732
686733
687734def _timestamp (value : str ) -> pd .Timestamp :
@@ -690,7 +737,9 @@ def _timestamp(value: str) -> pd.Timestamp:
690737
691738def _event (value : pd .Timestamp , action : str , payload : dict [str , object ]) -> str :
692739 milliseconds = int (value .timestamp () * 1_000 )
693- return f"{ milliseconds } ;local;{ action } ;{ json .dumps (payload , separators = (',' , ':' ))} \n "
740+ return (
741+ f"{ milliseconds } ;local;{ action } ;{ json .dumps (payload , separators = (',' , ':' ))} \n "
742+ )
694743
695744
696745def _sample_ids (study : _SyntheticStudy , registration : _Registration ) -> tuple [str , ...]:
@@ -793,14 +842,16 @@ def _deviation_minutes(
793842 tolerance = 15.0 if absolute else 5.0
794843 if absolute :
795844 lower , upper = (
796- (tolerance + 2 , tolerance + 20 )
797- if non_compliant
798- else (0 , tolerance - 1.0 )
845+ (tolerance + 2 , tolerance + 20 ) if non_compliant else (0 , tolerance - 1.0 )
799846 )
800847 magnitude = rng .uniform (lower , upper )
801848 return magnitude if rng .random () >= 0.5 else - magnitude
802849 if first_relative :
803- return rng .uniform (tolerance + 2 , tolerance + 20 ) if non_compliant else rng .uniform (0 , tolerance - 1.0 )
850+ return (
851+ rng .uniform (tolerance + 2 , tolerance + 20 )
852+ if non_compliant
853+ else rng .uniform (0 , tolerance - 1.0 )
854+ )
804855 if relative_interval is None :
805856 raise RuntimeError ("Relative sample deviations require their interval." )
806857 lower , upper = (
@@ -875,7 +926,9 @@ def _write_study(
875926 participant_root = logs_root / participant
876927 participant_root .mkdir ()
877928 canonical_day = 0
878- for registration_number , configuration in enumerate (study .registrations , start = 1 ):
929+ for registration_number , configuration in enumerate (
930+ study .registrations , start = 1
931+ ):
879932 metadata_timestamp = _timestamp (f"{ configuration .registration_date } 18:00" )
880933 metadata_file = participant_root / _source_filename (
881934 study , participant , metadata_timestamp
@@ -905,7 +958,9 @@ def _write_study(
905958 events : list [tuple [pd .Timestamp , str , dict [str , object ]]] = []
906959 day_key = (participant , registration_number , registration_day )
907960 if day_key not in missing_awakenings :
908- action = "alarm_stop" if rng .random () < 0.35 else "spontaneous_awakening"
961+ action = (
962+ "alarm_stop" if rng .random () < 0.35 else "spontaneous_awakening"
963+ )
909964 events .append ((awakening , action , {"id" : - 1 }))
910965 for position , (sample_id , sampling_time ) in enumerate (
911966 zip (sample_ids , sample_times , strict = True ), start = 1
@@ -948,9 +1003,7 @@ def _write_study(
9481003 "date" : collection_date ,
9491004 "awakening_time" : awakening .strftime ("%H:%M" ),
9501005 ** {
951- f"sampling_time_{ position } " : sampling_time .strftime (
952- "%H:%M"
953- )
1006+ f"sampling_time_{ position } " : sampling_time .strftime ("%H:%M" )
9541007 for position , sampling_time in enumerate (
9551008 sample_times , start = 1
9561009 )
@@ -1003,9 +1056,7 @@ def _write_cortisol_data(
10031056 for participant in participants :
10041057 for position , multiplier in enumerate (profile , start = 1 ):
10051058 condition_multiplier = (
1006- 1.12
1007- if condition .casefold () == "challenge"
1008- else 1.0
1059+ 1.12 if condition .casefold () == "challenge" else 1.0
10091060 )
10101061 value = (
10111062 participant_baselines [participant ]
@@ -1128,7 +1179,9 @@ def _write_issue_decisions(
11281179 create_cortisol_data : bool ,
11291180 validate : bool ,
11301181) -> None :
1131- folders = {participant : study_root / "logs" / participant for participant in participants }
1182+ folders = {
1183+ participant : study_root / "logs" / participant for participant in participants
1184+ }
11321185 raw_logs , source_audit = io .load_raw_logs_from_participant_folders (
11331186 folders , create_report = True
11341187 )
@@ -1177,7 +1230,9 @@ def _write_issue_decisions(
11771230 if final_report ["issues" ]["resolution_status" ].eq ("unresolved" ).any ():
11781231 raise RuntimeError ("Final conversion still contains unresolved issues." )
11791232 samples = logs .extract_sample_events_from_summary (final )
1180- expected_samples = len (participants ) * study .canonical_day_count * study .sample_count
1233+ expected_samples = (
1234+ len (participants ) * study .canonical_day_count * study .sample_count
1235+ )
11811236 if len (samples ) != expected_samples or samples ["sampling_time" ].isna ().any ():
11821237 raise RuntimeError ("Final conversion does not contain every expected sample." )
11831238 actual_non_compliant = samples ["sample_compliant" ].eq (False ).sum ()
@@ -1189,10 +1244,10 @@ def _write_issue_decisions(
11891244 source_values = set (samples ["sampling_time_source" ].dropna ())
11901245 if not source_values .issubset ({"app" , "manual_diary" }):
11911246 raise RuntimeError (f"Unexpected sampling-time sources: { source_values } ." )
1192- if samples ["sampling_time_source" ].eq ("manual_diary" ).sum () != len (
1193- missing_samples
1194- ):
1195- raise RuntimeError ( "Manual diary patch count does not match missing raw samples." )
1247+ if samples ["sampling_time_source" ].eq ("manual_diary" ).sum () != len (missing_samples ):
1248+ raise RuntimeError (
1249+ "Manual diary patch count does not match missing raw samples."
1250+ )
11961251 if create_cortisol_data :
11971252 cortisol = pd .read_csv (study_root / "cortisol.csv" ).set_index (
11981253 ["participant" , "day" , "sample_position" , "condition" ]
0 commit comments