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.
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 /priv… <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.
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>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-15 00:00:00 2025-05-15 06: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.
After conversion, load the laboratory measurements with
read_saliva(), merge them with merge_saliva(),
and continue with the saliva-analysis vignette.