Skip to content

Commit b8202fe

Browse files
authored
csv use empty string instead of nulls (#1766)
1 parent 0420158 commit b8202fe

5 files changed

Lines changed: 69 additions & 7 deletions

File tree

cdisc_rules_engine/services/reporting/base_report_data.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,19 @@ def __init__(
3939
)
4040

4141
@staticmethod
42-
def process_values(values: list[str]) -> list[str]:
43-
if not values or values is None:
44-
return ["null"]
42+
def process_values(
43+
values: list[str | None], null_placeholder: str = "null"
44+
) -> list[str]:
45+
if not values:
46+
return [null_placeholder]
4547
processed_values = []
4648
for value in values:
4749
if value is None:
48-
processed_values.append("null")
50+
processed_values.append(null_placeholder)
4951
continue
5052
value = value.strip()
5153
if value == "" or value.lower() == "nan":
52-
processed_values.append("null")
54+
processed_values.append(null_placeholder)
5355
else:
5456
processed_values.append(value)
5557
return processed_values

cdisc_rules_engine/services/reporting/sdtm_report_data.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,8 @@ def get_csv_rows(self) -> tuple[list[str], list[list[str]]]:
356356
variables = issue.get("variables") or []
357357
values = issue.get("values") or []
358358
for variable, value in zip(variables, values):
359-
rows.append([dataset, record, variable, str(value)])
359+
csv_value = "" if value in (None, "null") else value
360+
rows.append([dataset, record, variable, csv_value])
360361
return header, rows
361362

362363
def get_rules_report_data(self) -> list[dict]:

cdisc_rules_engine/services/reporting/usdm_report_data.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ def get_csv_rows(self) -> tuple[list[str], list[list[str]]]:
253253
attributes = issue.get("attributes") or []
254254
values = issue.get("values") or []
255255
for attribute, value in zip(attributes, values):
256-
rows.append([path, attribute, str(value)])
256+
csv_value = "" if value in (None, "null") else value
257+
rows.append([path, attribute, csv_value])
257258
return header, rows
258259

259260
def get_rules_report_data(self) -> list[dict]:

tests/unit/test_services/test_reporting/test_sdtm_report.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,38 @@ def test_get_csv_rows_empty_results():
187187
assert rows == []
188188

189189

190+
def test_get_csv_rows_preserves_blank_values_for_none_and_empty_string(
191+
mock_validation_results,
192+
):
193+
mock_validation_results[0].results[0]["errors"][0]["value"]["AESTDY"] = None
194+
mock_validation_results[0].results[0]["errors"][0]["value"]["DOMAIN"] = ""
195+
report = SDTMReportData(
196+
[],
197+
["test"],
198+
mock_validation_results,
199+
10.1,
200+
MagicMock(define_xml_path=None, max_errors_per_rule=(None, False)),
201+
)
202+
203+
_, rows = report.get_csv_rows()
204+
assert [
205+
row[3] for row in rows if row[1] == "1" and row[2] in {"AESTDY", "DOMAIN"}
206+
] == [
207+
"",
208+
"",
209+
]
210+
211+
details = report.get_detailed_data()
212+
detail_row = next(
213+
row
214+
for row in details
215+
if row["core_id"] == "CORE1"
216+
and row["row"] == 1
217+
and row["variables"] == ["AESTDY", "DOMAIN"]
218+
)
219+
assert detail_row["values"] == ["null", "null"]
220+
221+
190222
def test_no_errors_when_none_value_in_one_of_the_records(mock_validation_results):
191223
# forcing None and str comparison in summary and details
192224
mock_validation_results[0].id = None

tests/unit/test_services/test_reporting/test_usdm_report.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,32 @@ def test_get_csv_rows_empty_results():
184184
assert rows == []
185185

186186

187+
def test_get_csv_rows_preserves_blank_values_for_none_and_empty_string(
188+
mock_validation_results,
189+
):
190+
mock_validation_results[0].results[0]["errors"][0]["value"]["AESTDY"] = None
191+
mock_validation_results[0].results[0]["errors"][0]["value"]["DOMAIN"] = ""
192+
report = USDMReportData(
193+
[],
194+
["test"],
195+
mock_validation_results,
196+
10.1,
197+
MagicMock(define_xml_path=None, max_errors_per_rule=(None, False)),
198+
)
199+
200+
_, rows = report.get_csv_rows()
201+
assert any(row[1] == "AESTDY" and row[2] == "" for row in rows)
202+
assert any(row[1] == "DOMAIN" and row[2] == "" for row in rows)
203+
204+
details = report.get_detailed_data()
205+
detail_row = next(
206+
row
207+
for row in details
208+
if row["core_id"] == "CORE1" and row["attributes"] == ["AESTDY", "DOMAIN"]
209+
)
210+
assert detail_row["values"] == ["null", "null"]
211+
212+
187213
def test_no_errors_when_none_value_in_one_of_the_records(mock_validation_results):
188214
# forcing None and str comparison in summary and details
189215
mock_validation_results[0].id = None

0 commit comments

Comments
 (0)