Repository: epiverse-trace/episoap
Pull request #163: add get_data_type(), a data type detection function for severity pipeline
Author: login: Attalowe
Created: 2025-03-05T16:18:42Z | Merged: 2025-03-26T10:04:49Z

## Purpose

This PR adds `get_data_type()`, a utility function to automatically classify input data for severity analyses. It supports:
- Count data (via `total_count`/`total_death` arguments)
- `incidence`-class objects
- Linelist/incidence data frames
- Error handling for unrecognized formats

## Changes
- New function in `R/get_data_type.R`
- Case-insensitive column name checks
- Validation logic for required columns (`date`, `cases`, `dead`)
- Linelist keyword matching (e.g., "id", "onset", "outcome")
- Roxygen2 documentation with other files (to be push later)

## Testing
- Includes validation for:
  - Count data detection
  - `incidence` object handling
  - Data frame type classification (linelist vs. incidence)
  - Error cases

## Notes
- usethis and devtools was use.


Diff:
diff --git a/NAMESPACE b/NAMESPACE
index 3d380b90..572082ed 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -1,7 +1,9 @@
 # Generated by roxygen2: do not edit by hand
 
 export(calculate_cfr)
+export(calculate_cfr_from_counts)
 export(convert_to_incidence)
+export(get_data_type)
 export(get_sequential_dates)
 export(get_severity)
 export(list_templates)
diff --git a/R/get_data_type.R b/R/get_data_type.R
new file mode 100644
index 00000000..82d7bea4
--- /dev/null
+++ b/R/get_data_type.R
@@ -0,0 +1,113 @@
+
+
+#' Determine input data type for the severity pipeline
+#'
+#' @param data A dataframe-like object that can be either data.frame, linelist or incidence. Default is NULL.
+#' @param total_count A numeric with the total number of cases. Default is NULL.
+#' @param total_death A numeric with the total number of deaths. Default is NULL.
+#'
+#' @returns  A character with one of the following values: "count_data", "linelist" or "incidence".
+#' @export
+#'
+#' @examples
+#'
+#' # 1. COUNT DATA EXAMPLE
+#' # When totals are provided directly
+#' get_data_type(total_count = 1500, total_death = 75)  # Returns "count_data"
+#'
+#' # 2. INCIDENCE OBJECT EXAMPLE
+#' # When using an incidence object
+#' dummy_inc <- structure(list(date = Sys.Date(), cases = 100, dead = 5),
+#'                        class = "incidence")
+#' get_data_type(data = dummy_inc)  # Returns "incidence"
+#'
+#' # 3. LINELIST EXAMPLE
+#' # Detailed case-based data with key identifiers
+#' linelist_df <- data.frame(
+#'   ID = 1:100,
+#'   ONSET_DATE = Sys.Date() - 1:100,
+#'   REPORT_DATE = Sys.Date(),
+#'   AGE = sample(5:80, 100, replace = TRUE),
+#'   OUTCOME = sample(c("Fatal", "Recovered"), 100, replace = TRUE)
+#' )
+#' get_data_type(data = linelist_df)  # Returns "linelist"
+#'
+#' # 4. SIMPLE INCIDENCE DATA FRAME
+#' # Minimal time-based case counts
+#' inc_df <- data.frame(
+#'   Date = seq.Date(Sys.Date(), by = "day", length.out = 10),
+#'   Cases = sample(10:50, 10),
+#'   Dead = sample(20:100, 10),
+#' )
+#' get_data_type(data = inc_df)  # Returns "incidence"
+#'
+#' # 5. ERROR CASE
+#' # Missing both data and counts
+#' try(get_data_type())  # Throws  error message
+
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
+  # validate inputs
+  checkmate::assert(
+    checkmate::check_null(data),
+    checkmate::check_data_frame(data),
+    checkmate::check_class(data, classes = "linelist"),
+    checkmate::check_class(data, classes = "incidence"),
+    combine = "or"
+  )
+  # Check if 'total_count'  and total_death' are a single numeric value or NULL and are non-negative
+  checkmate::assert_number(total_count, null.ok = TRUE, lower = 0)
+  checkmate::assert_number(total_death, null.ok = TRUE, lower = 0)
+
+  # Check for count data
+  if ( !is.null(total_count) && !is.null(total_death)) {
+    return("count_data")
+  }
+
+
+  #  Check for incidence objects
+  if (inherits(data, "incidence")) {
+    return("incidence")
+  }
+
+  # check for data.frame/linelist
+  if (inherits(data, "data.frame")) {
+    # Convert column names to lowercase for consistent checks
+    actual_cols <- tolower(names(data))
+    required_incidence <- c("date", "cases", "dead") # atleast
+    # linelist features
+    linelist_keywords <- c("id", "case", "date", "onset", "report",
+                           "age", "sex", "gender", "outcome", "symptom", "hospital")
+
+
+    #  Incident data check
+    if (all(required_incidence %in% actual_cols)) {
+      # Check if pure incident or has extras
+      if (length(actual_cols) == 3) {
+        return("incidence")
+      } else {
+        # Check extra columns for linelist features
+        extra_cols <- actual_cols[!actual_cols %in% required_incidence]
+        has_linelist <- any(extra_cols %in% linelist_keywords)
+        return(ifelse(has_linelist, "linelist", "incidence"))
+      }
+    }
+
+
+    # Count matches in original column names (case-insensitive)
+    col_matches <- grepl(paste(linelist_keywords, collapse = "|"),
+                         names(data), ignore.case = TRUE)
+
+    if (sum(col_matches) >= 4) {
+      return("linelist")
+    }
+
+  }
+
+  # Default/error case
+  stop("unknown_data_type! Either provide a non-negative value for  total_count and total_death arguements or  a dataframe-like object (data.frame, linelist or incidence),in the data arguement")
+}
+
+
+
+
diff --git a/man/get_data_type.Rd b/man/get_data_type.Rd
new file mode 100644
index 00000000..61225e2a
--- /dev/null
+++ b/man/get_data_type.Rd
@@ -0,0 +1,57 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/get_data_type.R
+\name{get_data_type}
+\alias{get_data_type}
+\title{Determine input data type for the severity pipeline}
+\usage{
+get_data_type(data = NULL, total_count = NULL, total_death = NULL)
+}
+\arguments{
+\item{data}{A dataframe-like object that can be either data.frame, linelist or incidence. Default is NULL.}
+
+\item{total_count}{A numeric with the total number of cases. Default is NULL.}
+
+\item{total_death}{A numeric with the total number of deaths. Default is NULL.}
+}
+\value{
+A character with one of the following values: "count_data", "linelist" or "incidence".
+}
+\description{
+Determine input data type for the severity pipeline
+}
+\examples{
+
+# 1. COUNT DATA EXAMPLE
+# When totals are provided directly
+get_data_type(total_count = 1500, total_death = 75)  # Returns "count_data"
+
+# 2. INCIDENCE OBJECT EXAMPLE
+# When using the incidence package's format
+dummy_inc <- structure(list(date = Sys.Date(), cases = 100, dead = 5),
+                       class = "incidence")
+get_data_type(data = dummy_inc)  # Returns "incidence"
+
+# 3. LINELIST EXAMPLE
+# Detailed case-based data with key identifiers
+linelist_df <- data.frame(
+  ID = 1:100,
+  ONSET_DATE = Sys.Date() - 1:100,
+  REPORT_DATE = Sys.Date(),
+  AGE = sample(5:80, 100, replace = TRUE),
+  OUTCOME = sample(c("Fatal", "Recovered"), 100, replace = TRUE)
+)
+get_data_type(data = linelist_df)  # Returns "linelist"
+
+# 4. SIMPLE INCIDENCE DATA FRAME
+# Minimal time-based case counts
+inc_df <- data.frame(
+  Date = seq.Date(Sys.Date(), by = "day", length.out = 10),
+  Cases = sample(10:50, 10),
+  Dead = sample(20:100, 10),
+)
+get_data_type(data = inc_df)  # Returns "incidence"
+
+# 5. ERROR CASE
+# Missing both data and counts
+try(get_data_type())  # Throws  error message
+}
diff --git a/tests/testthat/test-get_data_type.R b/tests/testthat/test-get_data_type.R
new file mode 100644
index 00000000..3039ba09
--- /dev/null
+++ b/tests/testthat/test-get_data_type.R
@@ -0,0 +1,99 @@
+test_that("get_data_type correctly identifies count data inputs", {
+  # Basic count data
+  expect_equal(get_data_type(total_count = 100, total_death = 5), "count_data")
+
+  # Zero values allowed
+  expect_equal(get_data_type(total_count = 0, total_death = 0), "count_data")
+
+  # Partial count inputs should error
+  expect_error(get_data_type(total_count = 100))
+  expect_error(get_data_type(total_death = 5))
+})
+
+test_that("get_data_type correctly handles incidence objects", {
+  # Pure incidence object
+  inc_obj <- structure(list(date = Sys.Date(), cases = 10, dead = 2),
+                       class = "incidence")
+  expect_equal(get_data_type(data = inc_obj), "incidence")
+
+  # Data frame that should be recognized as incidence
+  inc_df <- data.frame(
+    DATE = seq.Date(Sys.Date(), length.out = 5, by = "day"),
+    CASES = c(10, 15, 20, 18, 12),
+    DEAD = c(1, 2, 3, 1, 2)
+  )
+  expect_equal(get_data_type(data = inc_df), "incidence")
+
+  # Incidence data with extra non-linelist columns
+  inc_extra <- cbind(inc_df, notes = c("a", "b", "c", "d", "e"))
+  expect_equal(get_data_type(data = inc_extra), "incidence")
+
+  # Hybrid data with incidence columns + linelist features
+  hybrid_df <- data.frame(
+    date = Sys.Date(),
+    cases = 10,
+    dead = 2,
+    age = 35,
+    facility = "Hospital X"
+  )
+  expect_equal(get_data_type(data = hybrid_df), "incidence")
+})
+
+
+test_that("get_data_type correctly identifies linelist data", {
+  # Full linelist structure
+  linelist_df <- data.frame(
+    case_id = 1:10,
+    onset_date = Sys.Date() - 9:0,
+    report_date = Sys.Date(),
+    age = sample(5:80, 10),
+    outcome = sample(c("fatal", "recovered"), 10, replace = TRUE),
+    hospital = sample(c("A", "B", "C"), 10, replace = TRUE)
+  )
+  expect_equal(get_data_type(data = linelist_df), "linelist")
+
+  # Minimal linelist with 4 identifiers
+  minimal_linelist <- data.frame(
+    ID = 1:5,
+    ONSET = Sys.Date() - 4:0,
+    GENDER = sample(c("M", "F"), 5, replace = TRUE),
+    OUTCOME = rep("fatal", 5)
+  )
+  expect_equal(get_data_type(data = minimal_linelist), "linelist")
+})
+
+
+test_that("get_data_type handles error conditions ", {
+  # Missing all arguments
+  expect_error(get_data_type(), "unknown_data_type")
+
+  # Invalid count values
+  expect_error(get_data_type(total_count = -5, total_death = 10))
+  expect_error(get_data_type(total_count = 100, total_death = -1))
+  expect_error(get_data_type(total_count = -10, total_death = -15))
+
+  # Invalid data types
+  expect_error(get_data_type(data = "not a dataframe-like object, linelist or incidence"))
+  expect_error(get_data_type(total_count = "100"))
+
+  # Ambiguous data frame
+  ambiguous_df <- data.frame(
+    random_col1 = 1:5,
+    random_col2 = letters[1:5]
+  )
+  expect_error(get_data_type(data = ambiguous_df), "unknown_data_type")
+})
+
+test_that("get_data_type handles column name edge cases", {
+  # Case-insensitive column matching
+  weird_case_df <- data.frame(
+    DaTe = Sys.Date(),
+    cAsEs = 10,
+    dEaD = 2,
+    sEx = "M"
+  )
+  expect_equal(get_data_type(data = weird_case_df), "linelist")
+
+
+})
+


Reviews:
user: login: Karim-Mane
body: great work!

Please add the tests files as well.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-12T13:11:11Z
user: login: Attalowe
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-17T15:08:48Z
user: login: Karim-Mane
body: Well done - I left few comments on it.

It looks great to me and ready for merge after fixing these minor issues.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T09:36:51Z
user: login: Karim-Mane
body: Looks good to me
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-25T11:56:24Z

Review comments:
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
#' Determine input data type for the severity pipeline
```
created_at: 2025-03-12T12:45:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
#' @param data A dataframe-like object that can be either data.frame, linelist or incidence. Default is NULL.
```
created_at: 2025-03-12T12:48:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
#' @param total_count A numeric with the total number of cases. Default is NULL.
```
created_at: 2025-03-12T12:48:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
#' @param total_death A numeric with the total number of deaths. Default is NULL.
```
created_at: 2025-03-12T12:49:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
+#'
+#' @returns One of "count_data", "linelist", "incidence".
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
#' @returns A character with one of the following values: "count_data", "linelist" or "incidence".
```
created_at: 2025-03-12T12:51:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
+#'
+#' @returns One of "count_data", "linelist", "incidence".
+#' @export
+#'
+#' @examples
path: R/get_data_type.R
user: login: Karim-Mane
body: Please add an example 
created_at: 2025-03-12T12:51:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
+#'
+#' @returns One of "count_data", "linelist", "incidence".
+#' @export
+#'
+#' @examples
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
path: R/get_data_type.R
user: login: Karim-Mane
body: use {checkmate} to check the values provided in each argument.
created_at: 2025-03-12T12:52:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
+#'
+#' @returns One of "count_data", "linelist", "incidence".
+#' @export
+#'
+#' @examples
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
+  # Check for count data
+  if ( !is.null(total_count) && !is.null(total_death)){
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
count_data <- c(total_count, total_death)
  if (length(count_data) == 2){
```
created_at: 2025-03-12T12:57:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
+#'
+#' @returns One of "count_data", "linelist", "incidence".
+#' @export
+#'
+#' @examples
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
+  # Check for count data
+  if ( !is.null(total_count) && !is.null(total_death)){
+    return("count_data")
+  }
+
+  # 2. Check for incidence objects
+  if (inherits(data, "incidence")) {
+    return("incidence")
+  }
+
+  if (inherits(data, "data.frame")) {
+    # Convert column names to lowercase for consistent checks
+    actual_cols <- tolower(names(data))
+    required_incidence <- c("date", "cases", "dead") # atleast
+    # linelist features
+    linelist_keywords <- c("id", "case", "date", "onset", "report",
+                           "age", "sex", "gender", "outcome", "symptom", "hospital")
+
+
+    # 3a. Incident data check
+    if (all(required_incidence %in% actual_cols)) {
+      # Check if pure incident or has extras
+      if (length(actual_cols) == 3) {
+        return("incidence")
+      } else {
+        # Check extra columns for linelist features
+        extra_cols <- actual_cols[!actual_cols %in% required_incidence]
+        has_linelist <- any(extra_cols %in% linelist_keywords)
+        return(ifelse(has_linelist, "linelist", "incidence"))
+      }
+    }
+
+
+    # Count matches in original column names (case-insensitive)
+    col_matches <- grepl(paste(linelist_keywords, collapse = "|"),
+                         names(data), ignore.case = TRUE)
+
+    if (sum(col_matches) >= 4) {
+      return("linelist")
+    }
+
+  }
+
+  # Default/error case
+  stop("unknown_data_type Provide count args, linelist, or incidence.")
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
  stop("Unknown data type! Either provide a value for total_count and total_death arguements or  only a value for data arguement.")
```
created_at: 2025-03-12T13:09:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+
+
+#' Determine Input Data Type for Severity Pipelinele
+#'
+#' @param data A dataframe, incidence object, or NULL.
+#' @param total_count Numeric total cases (optional).
+#' @param total_death Numeric total deaths (optional).
+#'
+#' @returns One of "count_data", "linelist", "incidence".
+#' @export
+#'
+#' @examples
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
+  # Check for count data
+  if ( !is.null(total_count) && !is.null(total_death)){
path: R/get_data_type.R
user: login: Attalowe
body: refactor: Remove redundant length check for `count_data`

- The check `length(count_data) == 2` is redundant because `total_count` and `total_death` 
are already validated to be non-NULL in the initial condition.
- Checkmate ensures `total_count` and `total_death` are scalars, so `count_data` will always have length 2 if the initial condition passes.
created_at: 2025-03-17T15:08:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,119 @@
+
+
+#' Determine input data type for the severity pipeline
+#'
+#' @param data A dataframe-like object that can be either data.frame, linelist or incidence. Default is NULL.
+#' @param total_count A numeric with the total number of cases. Default is NULL.
+#' @param total_death A numeric with the total number of deaths. Default is NULL.
+#'
+#' @returns  A character with one of the following values: "count_data", "linelist" or "incidence".
+#' @export
+#'
+#' @examples
+#'
+#' # 1. COUNT DATA EXAMPLE
+#' # When totals are provided directly
+#' get_data_type(total_count = 1500, total_death = 75)  # Returns "count_data"
+#'
+#' # 2. INCIDENCE OBJECT EXAMPLE
+#' # When using the incidence package's format
path: R/get_data_type.R
user: login: Karim-Mane
body: ```suggestion
#' # When using an incidence object
```
created_at: 2025-03-25T09:21:55Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,119 @@
+
+
+#' Determine input data type for the severity pipeline
+#'
+#' @param data A dataframe-like object that can be either data.frame, linelist or incidence. Default is NULL.
+#' @param total_count A numeric with the total number of cases. Default is NULL.
+#' @param total_death A numeric with the total number of deaths. Default is NULL.
+#'
+#' @returns  A character with one of the following values: "count_data", "linelist" or "incidence".
+#' @export
+#'
+#' @examples
+#'
+#' # 1. COUNT DATA EXAMPLE
+#' # When totals are provided directly
+#' get_data_type(total_count = 1500, total_death = 75)  # Returns "count_data"
+#'
+#' # 2. INCIDENCE OBJECT EXAMPLE
+#' # When using the incidence package's format
+#' dummy_inc <- structure(list(date = Sys.Date(), cases = 100, dead = 5),
+#'                        class = "incidence")
+#' get_data_type(data = dummy_inc)  # Returns "incidence"
+#'
+#' # 3. LINELIST EXAMPLE
+#' # Detailed case-based data with key identifiers
+#' linelist_df <- data.frame(
+#'   ID = 1:100,
+#'   ONSET_DATE = Sys.Date() - 1:100,
+#'   REPORT_DATE = Sys.Date(),
+#'   AGE = sample(5:80, 100, replace = TRUE),
+#'   OUTCOME = sample(c("Fatal", "Recovered"), 100, replace = TRUE)
+#' )
+#' get_data_type(data = linelist_df)  # Returns "linelist"
+#'
+#' # 4. SIMPLE INCIDENCE DATA FRAME
+#' # Minimal time-based case counts
+#' inc_df <- data.frame(
+#'   Date = seq.Date(Sys.Date(), by = "day", length.out = 10),
+#'   Cases = sample(10:50, 10),
+#'   Dead = sample(20:100, 10),
+#' )
+#' get_data_type(data = inc_df)  # Returns "incidence"
+#'
+#' # 5. ERROR CASE
+#' # Missing both data and counts
+#' try(get_data_type())  # Throws  error message
+
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
+  # validate inputs
+  checkmate::assert(
+    checkmate::check_null(data),
+    checkmate::check_data_frame(data),
+    checkmate::check_class(data, classes = "linelist"),
+    checkmate::check_class(data, classes = "incidence"),
+    combine = "or"
+  )
+
+  # Check if 'total_count'  and total_death' are a single numeric value or NULL
+  checkmate::assert_number(total_count, null.ok = TRUE)
path: R/get_data_type.R
user: login: Karim-Mane
body: add an extra argument here to specify that `total_count` and `total_death` should not be negative.
created_at: 2025-03-25T09:26:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,119 @@
+
+
+#' Determine input data type for the severity pipeline
+#'
+#' @param data A dataframe-like object that can be either data.frame, linelist or incidence. Default is NULL.
+#' @param total_count A numeric with the total number of cases. Default is NULL.
+#' @param total_death A numeric with the total number of deaths. Default is NULL.
+#'
+#' @returns  A character with one of the following values: "count_data", "linelist" or "incidence".
+#' @export
+#'
+#' @examples
+#'
+#' # 1. COUNT DATA EXAMPLE
+#' # When totals are provided directly
+#' get_data_type(total_count = 1500, total_death = 75)  # Returns "count_data"
+#'
+#' # 2. INCIDENCE OBJECT EXAMPLE
+#' # When using the incidence package's format
+#' dummy_inc <- structure(list(date = Sys.Date(), cases = 100, dead = 5),
+#'                        class = "incidence")
+#' get_data_type(data = dummy_inc)  # Returns "incidence"
+#'
+#' # 3. LINELIST EXAMPLE
+#' # Detailed case-based data with key identifiers
+#' linelist_df <- data.frame(
+#'   ID = 1:100,
+#'   ONSET_DATE = Sys.Date() - 1:100,
+#'   REPORT_DATE = Sys.Date(),
+#'   AGE = sample(5:80, 100, replace = TRUE),
+#'   OUTCOME = sample(c("Fatal", "Recovered"), 100, replace = TRUE)
+#' )
+#' get_data_type(data = linelist_df)  # Returns "linelist"
+#'
+#' # 4. SIMPLE INCIDENCE DATA FRAME
+#' # Minimal time-based case counts
+#' inc_df <- data.frame(
+#'   Date = seq.Date(Sys.Date(), by = "day", length.out = 10),
+#'   Cases = sample(10:50, 10),
+#'   Dead = sample(20:100, 10),
+#' )
+#' get_data_type(data = inc_df)  # Returns "incidence"
+#'
+#' # 5. ERROR CASE
+#' # Missing both data and counts
+#' try(get_data_type())  # Throws  error message
+
+get_data_type <- function( data = NULL, total_count = NULL, total_death = NULL ){
+
+  # validate inputs
+  checkmate::assert(
+    checkmate::check_null(data),
+    checkmate::check_data_frame(data),
+    checkmate::check_class(data, classes = "linelist"),
+    checkmate::check_class(data, classes = "incidence"),
+    combine = "or"
+  )
+
+  # Check if 'total_count'  and total_death' are a single numeric value or NULL
+  checkmate::assert_number(total_count, null.ok = TRUE)
+  checkmate::assert_number(total_death, null.ok = TRUE)
+
+
+# check  'total_count' and 'total_death' are non-negative
+  if (!is.null(total_count)) checkmate::assert_number(total_count, lower = 0)
path: R/get_data_type.R
user: login: Karim-Mane
body: This is not necessary. Just add `lower = 0` in the above check.
created_at: 2025-03-25T09:27:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,99 @@
+test_that("get_data_type correctly identifies count data inputs", {
+  # Basic count data
+  expect_equal(get_data_type(total_count = 100, total_death = 5), "count_data")
+
+  # Zero values allowed
+  expect_equal(get_data_type(total_count = 0, total_death = 0), "count_data")
+
+  # Partial count inputs should error
+  expect_error(get_data_type(total_count = 100))
+  expect_error(get_data_type(total_death = 5))
+})
+
+test_that("get_data_type correctly handles incidence objects", {
+  # Pure incidence object
+  inc_obj <- structure(list(date = Sys.Date(), cases = 10, dead = 2),
+                       class = "incidence")
+  expect_equal(get_data_type(data = inc_obj), "incidence")
+
+  # Data frame that should be recognized as incidence
+  inc_df <- data.frame(
+    DATE = seq.Date(Sys.Date(), length.out = 5, by = "day"),
+    CASES = c(10, 15, 20, 18, 12),
+    DEAD = c(1, 2, 3, 1, 2)
+  )
+  expect_equal(get_data_type(data = inc_df), "incidence")
+
+  # Incidence data with extra non-linelist columns
+  inc_extra <- cbind(inc_df, notes = c("a", "b", "c", "d", "e"))
+  expect_equal(get_data_type(data = inc_extra), "incidence")
+
+  # Hybrid data with incidence columns + linelist features
+  hybrid_df <- data.frame(
+    date = Sys.Date(),
+    cases = 10,
+    dead = 2,
+    patient_age = 35,
path: tests/testthat/test-get_data_type.R
user: login: Karim-Mane
body: ```suggestion
    age = 35,
```
created_at: 2025-03-25T09:33:31Z
author_association: MEMBER

PR comments:
user: login: Attalowe
created_at: 2025-03-20T16:26:32Z
body: @Karim-Mane I've addressed the requested changes. Could you please take another look?  thank you.  
author_association: NONE
user: login: Attalowe
created_at: 2025-03-25T11:34:26Z
body: @Karim-Mane All issues are addressed, and ready for your final review! 🚀
author_association: NONE
user: login: Karim-Mane
created_at: 2025-03-25T11:57:08Z
body: Please use `Rebase and merge` instead of `Squash and merge`
author_association: MEMBER
user: login: Attalowe
created_at: 2025-03-25T12:54:26Z
body: @Karim-Mane  done

author_association: NONE