Skip to contents

CARWatch turns app log exports into a study table with one planned position for every participant, study day, and saliva sample. The original log events remain unchanged. If information is missing or inconsistent, conversion produces a review report instead of silently guessing.

Import the raw logs

For studies stored as one folder per participant, pass a named vector of folders. The names become participant identifiers.

library(carwatch)

fixture <- system.file("extdata", "parity", "v1.0.0", package = "carwatch")
participant_dirs <- c(VP01 = file.path(fixture, "raw", "VP01"))

imported <- read_raw_logs_from_participant_dirs(
  participant_dirs,
  create_report = TRUE
)
raw_logs <- imported$raw_logs
head(imported$source_audit)
#> # A tibble: 1 × 8
#>   participant participant_folder       source archive_member logical_source_file
#>   <chr>       <fs::path>               <chr>  <chr>          <chr>              
#> 1 VP01        …/parity/v1.0.0/raw/VP01 /tmp/… NA             carwatch_parity_VP…
#> # ℹ 3 more variables: raw_event_count <int>, status <chr>, reason <chr>

The source audit records which CSV or ZIP input was selected and why another candidate was skipped.

Convert in two passes

The first pass reconstructs the registrations, study days, and planned samples. Use errors = "warn" so unresolved cases are returned for review.

first_pass <- convert_raw_logs(
  raw_logs,
  errors = "warn",
  create_report = TRUE
)
first_pass$report$issues
#> # A tibble: 0 × 15
#> # ℹ 15 variables: participant <chr>, day <chr>, sample_id <chr>, code <chr>,
#> #   registration <int>, registration_day <int>, sample_position <int>,
#> #   issue_id <chr>, message <chr>, details <chr>, proposed_action <chr>,
#> #   proposed_action_description <chr>, resolution_status <chr>,
#> #   user_decision <chr>, user_decision_value <chr>

For a real study, save the report with write_conversion_report(). Enter one allowed decision per issue in the CSV, or use conversion_report_editor() for an interactive review. Then read the decisions and rerun conversion against the same raw logs:

write_conversion_report(first_pass$report, "conversion-issues.csv")
decisions <- read_conversion_report("conversion-issues.csv")

study_results <- convert_raw_logs(
  raw_logs,
  issue_decisions = decisions,
  errors = "raise"
)

The bundled fixture has no unresolved issues, so its first-pass results can be used directly here.

study_results <- first_pass$results
as_study_days(study_results)
#> # A tibble: 1 × 17
#>   participant day   date                awakening_time      awakening_type      
#>   <chr>       <chr> <dttm>              <dttm>              <chr>               
#> 1 VP01        D1    2025-05-15 00:00:00 2025-05-15 06:06:40 spontaneous_awakeni…
#> # ℹ 12 more variables: mismatch_summary <chr>, registration <int>,
#> #   study_name <chr>, registration_day <int>, registration_sources <chr>,
#> #   possible_reregistration <lgl>, day_compliant <lgl>,
#> #   expected_sample_count <int>, recorded_sample_count <int>,
#> #   assessed_sample_count <int>, compliant_sample_count <int>,
#> #   non_compliant_samples <chr>
head(as_sample_events(study_results))
#> # A tibble: 2 × 32
#>   participant day   sample sampling_time       barcode   recorded_sample
#>   <chr>       <chr> <chr>  <dttm>              <chr>     <chr>          
#> 1 VP01        D1    tube-a 2025-05-15 06:06:40 barcode-a tube-a         
#> 2 VP01        D1    tube-b 2025-05-15 06:36:40 barcode-b tube-b         
#> # ℹ 26 more variables: sampling_time_source <chr>, sample_position <int>,
#> #   day_expected <int>, day_scanned <int>, schedule_type <chr>,
#> #   expected_interval_min <dbl>, actual_interval_min <dbl>,
#> #   scheduled_sampling_time <dttm>, time_deviation_min <dbl>,
#> #   sample_compliant <lgl>, awakening_time <dttm>, awakening_type <chr>,
#> #   registration <int>, study_name <chr>, registration_day <int>,
#> #   registration_sources <chr>, possible_reregistration <lgl>, …
summarize_compliance(study_results)
#> # A tibble: 2 × 8
#>   sample_position total_samples assessed_samples compliant_samples
#>             <int>         <int>            <int>             <int>
#> 1               1             1                1                 1
#> 2               2             1                1                 1
#> # ℹ 4 more variables: non_compliant_samples <int>, unassessed_samples <int>,
#> #   missing_sampling_time <int>, compliance_rate <dbl>

Save and restore Study Results

Complete Study Results use a three-header CSV format so day-, sample-, and variable-level information remains unambiguous.

results_file <- tempfile(fileext = ".csv")
write_study_results(study_results, results_file)
restored <- read_study_results(results_file)
restored
#> <carwatch_results: complete; 1 participants; 41 fields>
#> # A tibble: 1 × 42
#>   participant v1                  v2                  v3       v4       v5 v6   
#>   <chr>       <dttm>              <dttm>              <chr>    <chr> <int> <chr>
#> 1 VP01        2025-05-14 22:00:00 2025-05-15 04:06:40 spontan… NA        1 pari…
#> # ℹ 35 more variables: v7 <int>, v8 <chr>, v9 <lgl>, v10 <lgl>, v11 <int>,
#> #   v12 <int>, v13 <int>, v14 <int>, v15 <chr>, v16 <dttm>, v17 <chr>,
#> #   v18 <chr>, v19 <chr>, v20 <int>, v21 <int>, v22 <int>, v23 <chr>,
#> #   v24 <dbl>, v25 <dbl>, v26 <dttm>, v27 <dbl>, v28 <lgl>, v29 <dttm>,
#> #   v30 <chr>, v31 <chr>, v32 <chr>, v33 <int>, v34 <int>, v35 <int>,
#> #   v36 <chr>, v37 <dbl>, v38 <dbl>, v39 <dttm>, v40 <dbl>, v41 <lgl>

Use simple = TRUE only for display. A simplified result intentionally cannot be saved as a complete Study Results file.

Continue with laboratory data

After conversion, load the laboratory measurements with read_saliva(), merge them with merge_saliva(), and continue with the saliva-analysis vignette.