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.