From 2ee2c0fcba2a9d858b7eba73e3d0890971175f21 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 13 Aug 2026 10:15:06 -0400 Subject: [PATCH 1/6] min max date --- cdisc_rules_engine/operations/max_date.py | 24 +++++++++++------------ cdisc_rules_engine/operations/min_date.py | 16 ++++++++++----- cdisc_rules_engine/utilities/utils.py | 8 ++++++++ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/cdisc_rules_engine/operations/max_date.py b/cdisc_rules_engine/operations/max_date.py index 73d3fda88..870521f32 100644 --- a/cdisc_rules_engine/operations/max_date.py +++ b/cdisc_rules_engine/operations/max_date.py @@ -1,26 +1,24 @@ import pandas as pd from cdisc_rules_engine.operations.base_operation import BaseOperation +from cdisc_rules_engine.utilities.utils import format_date class MaxDate(BaseOperation): def _execute_operation(self): + data = pd.to_datetime( + self.params.dataframe[self.params.target], format="ISO8601" + ) if not self.params.grouping: - data = pd.to_datetime(self.params.dataframe[self.params.target]) max_date = data.max() if isinstance(max_date, pd._libs.tslibs.nattype.NaTType): result = "" else: - result = max_date.isoformat() + result = format_date(max_date) else: - result = self.params.dataframe.groupby( - self.params.grouping, as_index=False, group_keys=False - ).max() - if isinstance(result, pd.Series): - result = result.apply(lambda x: x.isoformat() if pd.notna(x) else "") - elif isinstance(result, pd.DataFrame): - for col in result.columns: - if pd.api.types.is_datetime64_any_dtype(result[col]): - result[col] = result[col].apply( - lambda x: x.isoformat() if pd.notna(x) else "" - ) + grouping_cols = self.params.grouping + if isinstance(grouping_cols, str): + grouping_cols = [grouping_cols] + group_keys = [self.params.dataframe[col] for col in grouping_cols] + max_dates = data.groupby(group_keys).transform("max") + result = max_dates.apply(format_date) return result diff --git a/cdisc_rules_engine/operations/min_date.py b/cdisc_rules_engine/operations/min_date.py index 106a916cc..469b66a16 100644 --- a/cdisc_rules_engine/operations/min_date.py +++ b/cdisc_rules_engine/operations/min_date.py @@ -1,18 +1,24 @@ import pandas as pd from cdisc_rules_engine.operations.base_operation import BaseOperation +from cdisc_rules_engine.utilities.utils import format_date class MinDate(BaseOperation): def _execute_operation(self): + data = pd.to_datetime( + self.params.dataframe[self.params.target], format="ISO8601" + ) if not self.params.grouping: - data = pd.to_datetime(self.params.dataframe[self.params.target]) min_date = data.min() if isinstance(min_date, pd._libs.tslibs.nattype.NaTType): result = "" else: - result = min_date.isoformat() + result = format_date(min_date) else: - result = self.params.dataframe.groupby( - self.params.grouping, as_index=False - ).min() + grouping_cols = self.params.grouping + if isinstance(grouping_cols, str): + grouping_cols = [grouping_cols] + group_keys = [self.params.dataframe[col] for col in grouping_cols] + min_dates = data.groupby(group_keys).transform("min") + result = min_dates.apply(format_date) return result diff --git a/cdisc_rules_engine/utilities/utils.py b/cdisc_rules_engine/utilities/utils.py index 40fe038d7..c277f2c64 100644 --- a/cdisc_rules_engine/utilities/utils.py +++ b/cdisc_rules_engine/utilities/utils.py @@ -414,3 +414,11 @@ def custom_str_conversion(x): elif isinstance(x, float): return f"{x:.0f}" if x.is_integer() else str(x).strip() return x + + +def format_date(d): + if pd.isna(d): + return "" + if d.hour == 0 and d.minute == 0 and d.second == 0 and d.microsecond == 0: + return d.strftime("%Y-%m-%d") + return d.isoformat() From 2eb5a8b7b719a6853d5fc1575a2603dbbe88f5c6 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 13 Aug 2026 12:47:37 -0400 Subject: [PATCH 2/6] rework --- cdisc_rules_engine/operations/max_date.py | 24 +++++++++++++++++------ cdisc_rules_engine/operations/min_date.py | 24 +++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cdisc_rules_engine/operations/max_date.py b/cdisc_rules_engine/operations/max_date.py index 870521f32..abf55c9a7 100644 --- a/cdisc_rules_engine/operations/max_date.py +++ b/cdisc_rules_engine/operations/max_date.py @@ -14,11 +14,23 @@ def _execute_operation(self): result = "" else: result = format_date(max_date) + return pd.Series(result, index=self.evaluation_dataset.index) + grouping_cols = self.params.grouping + if isinstance(grouping_cols, str): + grouping_cols = [grouping_cols] + + group_keys = [self.params.dataframe[col] for col in grouping_cols] + max_dates = data.groupby(group_keys).max().apply(format_date) + + # Build lookup: single key -> scalar dict, multi-key -> tuple-keyed dict + if len(grouping_cols) == 1: + lookup_keys = self.evaluation_dataset[grouping_cols[0]] else: - grouping_cols = self.params.grouping - if isinstance(grouping_cols, str): - grouping_cols = [grouping_cols] - group_keys = [self.params.dataframe[col] for col in grouping_cols] - max_dates = data.groupby(group_keys).transform("max") - result = max_dates.apply(format_date) + lookup_keys = pd.Series( + list(zip(*[self.evaluation_dataset[c] for c in grouping_cols])), + index=self.evaluation_dataset.index, + ) + + result = lookup_keys.map(max_dates).fillna("") + result.index = self.evaluation_dataset.index return result diff --git a/cdisc_rules_engine/operations/min_date.py b/cdisc_rules_engine/operations/min_date.py index 469b66a16..830740db7 100644 --- a/cdisc_rules_engine/operations/min_date.py +++ b/cdisc_rules_engine/operations/min_date.py @@ -14,11 +14,23 @@ def _execute_operation(self): result = "" else: result = format_date(min_date) + return pd.Series(result, index=self.evaluation_dataset.index) + + grouping_cols = self.params.grouping + if isinstance(grouping_cols, str): + grouping_cols = [grouping_cols] + + group_keys = [self.params.dataframe[col] for col in grouping_cols] + min_dates = data.groupby(group_keys).min().apply(format_date) + + if len(grouping_cols) == 1: + lookup_keys = self.evaluation_dataset[grouping_cols[0]] else: - grouping_cols = self.params.grouping - if isinstance(grouping_cols, str): - grouping_cols = [grouping_cols] - group_keys = [self.params.dataframe[col] for col in grouping_cols] - min_dates = data.groupby(group_keys).transform("min") - result = min_dates.apply(format_date) + lookup_keys = pd.Series( + list(zip(*[self.evaluation_dataset[c] for c in grouping_cols])), + index=self.evaluation_dataset.index, + ) + + result = lookup_keys.map(min_dates).fillna("") + result.index = self.evaluation_dataset.index return result From cbc161c9af1b5848e469d8e4d2b4d00c8c63018a Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 13 Aug 2026 13:02:24 -0400 Subject: [PATCH 3/6] update stale test --- tests/unit/test_operations/test_max_date.py | 5 +- tests/unit/test_operations/test_min_date.py | 96 +++++++++++++++++++-- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_operations/test_max_date.py b/tests/unit/test_operations/test_max_date.py index 50eaad04b..ed41129c7 100644 --- a/tests/unit/test_operations/test_max_date.py +++ b/tests/unit/test_operations/test_max_date.py @@ -3,6 +3,7 @@ from cdisc_rules_engine.models.dataset.pandas_dataset import PandasDataset from cdisc_rules_engine.operations.max_date import MaxDate from cdisc_rules_engine.models.operation_params import OperationParams +from cdisc_rules_engine.utilities.utils import format_date import pandas as pd import pytest @@ -17,14 +18,14 @@ [ ( {"dates": ["2001-01-01", "", "2022-01-05"]}, - pd.to_datetime("2022-01-05").isoformat(), + format_date(pd.to_datetime("2022-01-05")), PandasDataset, None, ), ({"dates": [None, None]}, "", PandasDataset, None), ( {"dates": ["2001-01-01", "", "2022-01-05"]}, - pd.to_datetime("2022-01-05").isoformat(), + format_date(pd.to_datetime("2022-01-05")), DaskDataset, None, ), diff --git a/tests/unit/test_operations/test_min_date.py b/tests/unit/test_operations/test_min_date.py index f15b3ad1f..0a3c14304 100644 --- a/tests/unit/test_operations/test_min_date.py +++ b/tests/unit/test_operations/test_min_date.py @@ -3,6 +3,7 @@ from cdisc_rules_engine.models.dataset.pandas_dataset import PandasDataset from cdisc_rules_engine.operations.min_date import MinDate from cdisc_rules_engine.models.operation_params import OperationParams +from cdisc_rules_engine.utilities.utils import format_date import pandas as pd import pytest @@ -13,31 +14,110 @@ @pytest.mark.parametrize( - "data, expected, dataset_type", + "data, expected, dataset_type, grouping", [ ( {"dates": ["2001-01-01", "", "2022-01-01"]}, - pd.to_datetime("2001-01-01").isoformat(), + format_date(pd.to_datetime("2001-01-01")), DaskDataset, + None, ), - ({"dates": [None, None]}, "", DaskDataset), + ({"dates": [None, None]}, "", DaskDataset, None), ( {"dates": ["2001-01-01", "", "2022-01-01"]}, - pd.to_datetime("2001-01-01").isoformat(), + format_date(pd.to_datetime("2001-01-01")), PandasDataset, + None, + ), + ({"dates": [None, None]}, "", PandasDataset, None), + ( + { + "dates": ["2025-10-10", "2025-10-15", "2025-12-02", "2025-12-11"], + "USUBJID": ["00002", "00002", "00003", "00003"], + }, + PandasDataset.from_records( + [ + { + "dates": "2025-10-10", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-10-15", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-12-02", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + { + "dates": "2025-12-11", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + ] + ), + PandasDataset, + ["USUBJID"], + ), + ( + { + "dates": ["2025-10-10", "2025-10-15", "2025-12-02", "2025-12-11"], + "USUBJID": ["00002", "00002", "00003", "00003"], + }, + DaskDataset.from_records( + [ + { + "dates": "2025-10-10", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-10-15", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-12-02", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + { + "dates": "2025-12-11", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + ] + ), + DaskDataset, + ["USUBJID"], ), - ({"dates": [None, None]}, "", PandasDataset), ], ) -def test_minimum(data, expected, operation_params: OperationParams, dataset_type): +def test_minimum( + data, + expected, + dataset_type, + grouping: str | None, + operation_params: OperationParams, +): config = ConfigService() cache = CacheServiceFactory(config).get_cache_service() data_service = DataServiceFactory(config, cache).get_data_service() operation_params.dataframe = dataset_type.from_dict(data) operation_params.target = "dates" + operation_params.grouping = grouping result = MinDate( operation_params, dataset_type.from_dict(data), cache, data_service ).execute() assert operation_params.operation_id in result - for val in result[operation_params.operation_id]: - assert val == expected + + if isinstance(expected, PandasDataset) and dataset_type is PandasDataset: + assert result.data.equals(expected.data) + elif isinstance(expected, DaskDataset) and dataset_type is DaskDataset: + assert expected.equals(result) + else: + for val in result[operation_params.operation_id]: + assert val == expected From dc7ba52f18e30ad8b2d403480d3b02fc8b9f1e86 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Thu, 13 Aug 2026 15:14:16 -0400 Subject: [PATCH 4/6] fixes --- cdisc_rules_engine/check_operators/helpers.py | 20 ++++++++++++++++++ cdisc_rules_engine/operations/max_date.py | 21 +++++++++++-------- cdisc_rules_engine/operations/min_date.py | 19 ++++++++++------- cdisc_rules_engine/utilities/utils.py | 8 ------- tests/unit/test_operations/test_max_date.py | 7 +++---- tests/unit/test_operations/test_min_date.py | 7 +++---- 6 files changed, 49 insertions(+), 33 deletions(-) diff --git a/cdisc_rules_engine/check_operators/helpers.py b/cdisc_rules_engine/check_operators/helpers.py index 0816380b0..7c787b298 100644 --- a/cdisc_rules_engine/check_operators/helpers.py +++ b/cdisc_rules_engine/check_operators/helpers.py @@ -334,6 +334,26 @@ def _truncate_by_precision( ) +def format_date_preserving_precision(original_str: str) -> str: + precision = detect_datetime_precision(original_str) + if precision is None: + return "" + dt = truncate_datetime_to_precision(original_str, precision) + + if precision >= DatePrecision.second: + return dt.strftime("%Y-%m-%dT%H:%M:%S") + elif precision == DatePrecision.minute: + return dt.strftime("%Y-%m-%dT%H:%M") + elif precision == DatePrecision.hour: + return dt.strftime("%Y-%m-%dT%H") + elif precision == DatePrecision.day: + return dt.strftime("%Y-%m-%d") + elif precision == DatePrecision.month: + return dt.strftime("%Y-%m") + else: # year + return dt.strftime("%Y") + + def _compare_with_inferred_precision( operator_func, target: str, diff --git a/cdisc_rules_engine/operations/max_date.py b/cdisc_rules_engine/operations/max_date.py index abf55c9a7..b6933fe6e 100644 --- a/cdisc_rules_engine/operations/max_date.py +++ b/cdisc_rules_engine/operations/max_date.py @@ -1,28 +1,31 @@ import pandas as pd from cdisc_rules_engine.operations.base_operation import BaseOperation -from cdisc_rules_engine.utilities.utils import format_date +from cdisc_rules_engine.check_operators.helpers import format_date_preserving_precision class MaxDate(BaseOperation): def _execute_operation(self): - data = pd.to_datetime( - self.params.dataframe[self.params.target], format="ISO8601" - ) + original = self.params.dataframe[self.params.target] + data = pd.to_datetime(original, format="ISO8601") + if not self.params.grouping: - max_date = data.max() - if isinstance(max_date, pd._libs.tslibs.nattype.NaTType): + if data.isna().all(): result = "" else: - result = format_date(max_date) + max_idx = data.idxmax() + result = format_date_preserving_precision(original.loc[max_idx]) return pd.Series(result, index=self.evaluation_dataset.index) + grouping_cols = self.params.grouping if isinstance(grouping_cols, str): grouping_cols = [grouping_cols] group_keys = [self.params.dataframe[col] for col in grouping_cols] - max_dates = data.groupby(group_keys).max().apply(format_date) + idx_of_max = data.groupby(group_keys).idxmax() + max_dates = idx_of_max.apply( + lambda idx: format_date_preserving_precision(original.loc[idx]) + ) - # Build lookup: single key -> scalar dict, multi-key -> tuple-keyed dict if len(grouping_cols) == 1: lookup_keys = self.evaluation_dataset[grouping_cols[0]] else: diff --git a/cdisc_rules_engine/operations/min_date.py b/cdisc_rules_engine/operations/min_date.py index 830740db7..02f0b03dc 100644 --- a/cdisc_rules_engine/operations/min_date.py +++ b/cdisc_rules_engine/operations/min_date.py @@ -1,19 +1,19 @@ import pandas as pd from cdisc_rules_engine.operations.base_operation import BaseOperation -from cdisc_rules_engine.utilities.utils import format_date +from cdisc_rules_engine.check_operators.helpers import format_date_preserving_precision class MinDate(BaseOperation): def _execute_operation(self): - data = pd.to_datetime( - self.params.dataframe[self.params.target], format="ISO8601" - ) + original = self.params.dataframe[self.params.target] + data = pd.to_datetime(original, format="ISO8601") + if not self.params.grouping: - min_date = data.min() - if isinstance(min_date, pd._libs.tslibs.nattype.NaTType): + if data.isna().all(): result = "" else: - result = format_date(min_date) + min_idx = data.idxmin() + result = format_date_preserving_precision(original.loc[min_idx]) return pd.Series(result, index=self.evaluation_dataset.index) grouping_cols = self.params.grouping @@ -21,7 +21,10 @@ def _execute_operation(self): grouping_cols = [grouping_cols] group_keys = [self.params.dataframe[col] for col in grouping_cols] - min_dates = data.groupby(group_keys).min().apply(format_date) + idx_of_min = data.groupby(group_keys).idxmin() + min_dates = idx_of_min.apply( + lambda idx: format_date_preserving_precision(original.loc[idx]) + ) if len(grouping_cols) == 1: lookup_keys = self.evaluation_dataset[grouping_cols[0]] diff --git a/cdisc_rules_engine/utilities/utils.py b/cdisc_rules_engine/utilities/utils.py index c277f2c64..40fe038d7 100644 --- a/cdisc_rules_engine/utilities/utils.py +++ b/cdisc_rules_engine/utilities/utils.py @@ -414,11 +414,3 @@ def custom_str_conversion(x): elif isinstance(x, float): return f"{x:.0f}" if x.is_integer() else str(x).strip() return x - - -def format_date(d): - if pd.isna(d): - return "" - if d.hour == 0 and d.minute == 0 and d.second == 0 and d.microsecond == 0: - return d.strftime("%Y-%m-%d") - return d.isoformat() diff --git a/tests/unit/test_operations/test_max_date.py b/tests/unit/test_operations/test_max_date.py index ed41129c7..eca4a63a8 100644 --- a/tests/unit/test_operations/test_max_date.py +++ b/tests/unit/test_operations/test_max_date.py @@ -3,8 +3,7 @@ from cdisc_rules_engine.models.dataset.pandas_dataset import PandasDataset from cdisc_rules_engine.operations.max_date import MaxDate from cdisc_rules_engine.models.operation_params import OperationParams -from cdisc_rules_engine.utilities.utils import format_date -import pandas as pd +from cdisc_rules_engine.check_operators.helpers import format_date_preserving_precision import pytest from cdisc_rules_engine.services.cache.cache_service_factory import CacheServiceFactory @@ -18,14 +17,14 @@ [ ( {"dates": ["2001-01-01", "", "2022-01-05"]}, - format_date(pd.to_datetime("2022-01-05")), + format_date_preserving_precision("2022-01-05"), PandasDataset, None, ), ({"dates": [None, None]}, "", PandasDataset, None), ( {"dates": ["2001-01-01", "", "2022-01-05"]}, - format_date(pd.to_datetime("2022-01-05")), + format_date_preserving_precision("2022-01-05"), DaskDataset, None, ), diff --git a/tests/unit/test_operations/test_min_date.py b/tests/unit/test_operations/test_min_date.py index 0a3c14304..7607792ee 100644 --- a/tests/unit/test_operations/test_min_date.py +++ b/tests/unit/test_operations/test_min_date.py @@ -3,8 +3,7 @@ from cdisc_rules_engine.models.dataset.pandas_dataset import PandasDataset from cdisc_rules_engine.operations.min_date import MinDate from cdisc_rules_engine.models.operation_params import OperationParams -from cdisc_rules_engine.utilities.utils import format_date -import pandas as pd +from cdisc_rules_engine.check_operators.helpers import format_date_preserving_precision import pytest from cdisc_rules_engine.services.cache.cache_service_factory import CacheServiceFactory @@ -18,14 +17,14 @@ [ ( {"dates": ["2001-01-01", "", "2022-01-01"]}, - format_date(pd.to_datetime("2001-01-01")), + format_date_preserving_precision("2001-01-01"), DaskDataset, None, ), ({"dates": [None, None]}, "", DaskDataset, None), ( {"dates": ["2001-01-01", "", "2022-01-01"]}, - format_date(pd.to_datetime("2001-01-01")), + format_date_preserving_precision("2001-01-01"), PandasDataset, None, ), From 4a3b5c38066200a5d5f7146655d31367f5d8fb56 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Fri, 14 Aug 2026 08:49:03 -0400 Subject: [PATCH 5/6] blank guard --- cdisc_rules_engine/operations/max_date.py | 7 +++++-- cdisc_rules_engine/operations/min_date.py | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cdisc_rules_engine/operations/max_date.py b/cdisc_rules_engine/operations/max_date.py index b6933fe6e..7b30ad0f4 100644 --- a/cdisc_rules_engine/operations/max_date.py +++ b/cdisc_rules_engine/operations/max_date.py @@ -23,9 +23,12 @@ def _execute_operation(self): group_keys = [self.params.dataframe[col] for col in grouping_cols] idx_of_max = data.groupby(group_keys).idxmax() max_dates = idx_of_max.apply( - lambda idx: format_date_preserving_precision(original.loc[idx]) + lambda idx: ( + "" + if pd.isna(idx) + else format_date_preserving_precision(original.loc[idx]) + ) ) - if len(grouping_cols) == 1: lookup_keys = self.evaluation_dataset[grouping_cols[0]] else: diff --git a/cdisc_rules_engine/operations/min_date.py b/cdisc_rules_engine/operations/min_date.py index 02f0b03dc..209c95a7f 100644 --- a/cdisc_rules_engine/operations/min_date.py +++ b/cdisc_rules_engine/operations/min_date.py @@ -23,7 +23,11 @@ def _execute_operation(self): group_keys = [self.params.dataframe[col] for col in grouping_cols] idx_of_min = data.groupby(group_keys).idxmin() min_dates = idx_of_min.apply( - lambda idx: format_date_preserving_precision(original.loc[idx]) + lambda idx: ( + "" + if pd.isna(idx) + else format_date_preserving_precision(original.loc[idx]) + ) ) if len(grouping_cols) == 1: From ca5db20687733b0c43677d2321ffe3f6bd630f17 Mon Sep 17 00:00:00 2001 From: Samuel Johnson Date: Tue, 18 Aug 2026 17:04:00 -0400 Subject: [PATCH 6/6] grouped NA protection --- cdisc_rules_engine/operations/max_date.py | 4 +- cdisc_rules_engine/operations/min_date.py | 4 +- tests/unit/test_operations/test_max_date.py | 82 +++++++++++++++++++++ tests/unit/test_operations/test_min_date.py | 82 +++++++++++++++++++++ 4 files changed, 170 insertions(+), 2 deletions(-) diff --git a/cdisc_rules_engine/operations/max_date.py b/cdisc_rules_engine/operations/max_date.py index 7b30ad0f4..bbefddfe2 100644 --- a/cdisc_rules_engine/operations/max_date.py +++ b/cdisc_rules_engine/operations/max_date.py @@ -21,7 +21,9 @@ def _execute_operation(self): grouping_cols = [grouping_cols] group_keys = [self.params.dataframe[col] for col in grouping_cols] - idx_of_max = data.groupby(group_keys).idxmax() + idx_of_max = data.groupby(group_keys).apply( + lambda s: s.idxmax() if s.notna().any() else pd.NA + ) max_dates = idx_of_max.apply( lambda idx: ( "" diff --git a/cdisc_rules_engine/operations/min_date.py b/cdisc_rules_engine/operations/min_date.py index 209c95a7f..ee0e26aea 100644 --- a/cdisc_rules_engine/operations/min_date.py +++ b/cdisc_rules_engine/operations/min_date.py @@ -21,7 +21,9 @@ def _execute_operation(self): grouping_cols = [grouping_cols] group_keys = [self.params.dataframe[col] for col in grouping_cols] - idx_of_min = data.groupby(group_keys).idxmin() + idx_of_min = data.groupby(group_keys).apply( + lambda s: s.idxmin() if s.notna().any() else pd.NA + ) min_dates = idx_of_min.apply( lambda idx: ( "" diff --git a/tests/unit/test_operations/test_max_date.py b/tests/unit/test_operations/test_max_date.py index eca4a63a8..735fafe89 100644 --- a/tests/unit/test_operations/test_max_date.py +++ b/tests/unit/test_operations/test_max_date.py @@ -93,6 +93,88 @@ DaskDataset, ["USUBJID"], ), + ( + { + "dates": [ + "2025-10-10", + "2025-10-15", + "2025-12-02", + "2025-12-11", + "", + "", + ], + "USUBJID": ["00002", "00002", "00003", "00003", "00004", "00004"], + }, + PandasDataset.from_records( + [ + { + "dates": "2025-10-10", + "USUBJID": "00002", + "operation_id": "2025-10-15", + }, + { + "dates": "2025-10-15", + "USUBJID": "00002", + "operation_id": "2025-10-15", + }, + { + "dates": "2025-12-02", + "USUBJID": "00003", + "operation_id": "2025-12-11", + }, + { + "dates": "2025-12-11", + "USUBJID": "00003", + "operation_id": "2025-12-11", + }, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + ] + ), + PandasDataset, + ["USUBJID"], + ), + ( + { + "dates": [ + "2025-10-10", + "2025-10-15", + "2025-12-02", + "2025-12-11", + "", + "", + ], + "USUBJID": ["00002", "00002", "00003", "00003", "00004", "00004"], + }, + DaskDataset.from_records( + [ + { + "dates": "2025-10-10", + "USUBJID": "00002", + "operation_id": "2025-10-15", + }, + { + "dates": "2025-10-15", + "USUBJID": "00002", + "operation_id": "2025-10-15", + }, + { + "dates": "2025-12-02", + "USUBJID": "00003", + "operation_id": "2025-12-11", + }, + { + "dates": "2025-12-11", + "USUBJID": "00003", + "operation_id": "2025-12-11", + }, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + ] + ), + DaskDataset, + ["USUBJID"], + ), ], ) def test_max_date( diff --git a/tests/unit/test_operations/test_min_date.py b/tests/unit/test_operations/test_min_date.py index 7607792ee..e5626be0c 100644 --- a/tests/unit/test_operations/test_min_date.py +++ b/tests/unit/test_operations/test_min_date.py @@ -93,6 +93,88 @@ DaskDataset, ["USUBJID"], ), + ( + { + "dates": [ + "2025-10-10", + "2025-10-15", + "2025-12-02", + "2025-12-11", + "", + "", + ], + "USUBJID": ["00002", "00002", "00003", "00003", "00004", "00004"], + }, + PandasDataset.from_records( + [ + { + "dates": "2025-10-10", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-10-15", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-12-02", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + { + "dates": "2025-12-11", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + ] + ), + PandasDataset, + ["USUBJID"], + ), + ( + { + "dates": [ + "2025-10-10", + "2025-10-15", + "2025-12-02", + "2025-12-11", + "", + "", + ], + "USUBJID": ["00002", "00002", "00003", "00003", "00004", "00004"], + }, + DaskDataset.from_records( + [ + { + "dates": "2025-10-10", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-10-15", + "USUBJID": "00002", + "operation_id": "2025-10-10", + }, + { + "dates": "2025-12-02", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + { + "dates": "2025-12-11", + "USUBJID": "00003", + "operation_id": "2025-12-02", + }, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + {"dates": "", "USUBJID": "00004", "operation_id": ""}, + ] + ), + DaskDataset, + ["USUBJID"], + ), ], ) def test_minimum(