diff --git a/pointblank/validate.py b/pointblank/validate.py index b77260c21..442a7c539 100644 --- a/pointblank/validate.py +++ b/pointblank/validate.py @@ -15958,10 +15958,16 @@ def get_tabular_report( elif assertion_type[i] in ["conjointly", "specially"]: column_text = "" else: - column_text = str(column) + # Handle both string columns and list columns + # For single-element lists like ['a'], display as 'a' + # For multi-element lists, display as comma-separated values + if isinstance(column, list): + column_text = ", ".join(str(c) for c in column) + else: + column_text = str(column) - # Apply underline styling for synthetic columns (using the purple color from the icon) - # Only apply styling if column_text is not empty and not a special marker + # Apply underline styling for synthetic columns; only apply styling if column_text is + # not empty and not a special marker if ( has_synthetic_column and column_text @@ -16080,6 +16086,32 @@ def get_tabular_report( else: # pragma: no cover values_upd.append(str(value)) # pragma: no cover + # Handle aggregation methods (col_sum_gt, col_avg_eq, etc.) + elif is_valid_agg(assertion_type[i]): + # Extract the value and tolerance from the values dict + agg_value = value.get("value") + tol_value = value.get("tol", 0) + + # Format the value (could be a number, Column, or ReferenceColumn) + if hasattr(agg_value, "__repr__"): + # For Column or ReferenceColumn objects, use their repr + value_str = repr(agg_value) + else: + value_str = str(agg_value) + + # Format tolerance - only show on second line if non-zero + if tol_value != 0: + # Format tolerance based on its type + if isinstance(tol_value, tuple): + # Asymmetric bounds: (lower, upper) + tol_str = f"tol=({tol_value[0]}, {tol_value[1]})" + else: + # Symmetric tolerance + tol_str = f"tol={tol_value}" + values_upd.append(f"{value_str}
{tol_str}") + else: + values_upd.append(value_str) + # If the assertion type is not recognized, add the value as a string else: # pragma: no cover values_upd.append(str(value)) # pragma: no cover diff --git a/tests/test_agg.py b/tests/test_agg.py index cef556e45..95513e5c8 100644 --- a/tests/test_agg.py +++ b/tests/test_agg.py @@ -910,6 +910,7 @@ def test_auto_ref_explicit_ref_still_works(method: str, auto_ref_equal_data): """Test that explicit ref() still works alongside auto-inference.""" data, reference = auto_ref_equal_data v = Validate(data=data, reference=reference) + # Explicit ref("val") should work the same as omitting value v = getattr(v, method)("val", ref("val")) v = v.interrogate() @@ -935,6 +936,7 @@ def test_auto_ref_eq_methods_with_tolerance(method: str): # Use larger values so tolerance calculation works (int(tol * ref) > 0) # Data avg=110, sum=440, sd≈12.91 data = pl.DataFrame({"val": [100.0, 105.0, 115.0, 120.0]}) + # Reference avg=100, sum=400, sd≈12.91 reference = pl.DataFrame({"val": [90.0, 95.0, 105.0, 110.0]}) @@ -959,7 +961,8 @@ def test_auto_ref_eq_methods_multiple_columns(method: str): reference = pl.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]}) v = Validate(data=data, reference=reference) - # Pass list of columns - should auto-infer ref(col) for each + + # Pass list of columns; should auto-infer ref(col) for each v = getattr(v, method)(["a", "b", "c"]) v = v.interrogate() v.assert_passing() @@ -1008,8 +1011,10 @@ def test_auto_ref_mixed_with_explicit_values(method: str, auto_ref_equal_data): data, reference = auto_ref_equal_data v = Validate(data=data, reference=reference) + # First call with auto-reference v = getattr(v, method)("val") + # Second call with explicit ref() v = getattr(v, method)("val", ref("val")) @@ -1017,5 +1022,120 @@ def test_auto_ref_mixed_with_explicit_values(method: str, auto_ref_equal_data): v.assert_passing() -if __name__ == "__main__": - pytest.main([__file__, "-sv"]) +# ============================================================================= +# Tests for validation report display formatting +# ============================================================================= + + +def test_agg_report_columns_display(): + """Test that the COLUMNS column displays column names without list brackets.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + + validation = Validate(data).col_sum_gt(columns="a", value=10).interrogate() + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Should display 'a' not '['a']' + assert "['a']" not in html + + # The column name should be present in the HTML + assert ">a<" in html + + +def test_agg_report_values_no_tolerance(): + """Test that VALUES column shows just the value when tolerance is 0.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + + validation = Validate(data).col_sum_gt(columns="a", value=10).interrogate() + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Should display just '10' and no tolerance info + assert ">10<" in html + + # Should NOT contain 'tol=' since tolerance is 0 + assert "tol=0" not in html + + +def test_agg_report_values_with_symmetric_tolerance(): + """Test that VALUES column shows value and tolerance on separate lines.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + + validation = Validate(data).col_avg_eq(columns="a", value=3, tol=0.5).interrogate() + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Should display value and tolerance separated by
+ assert "3
tol=0.5" in html + + +def test_agg_report_values_with_asymmetric_tolerance(): + """Test that VALUES column shows value and asymmetric tolerance tuple.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + + validation = Validate(data).col_sd_le(columns="a", value=2.0, tol=(0.1, 0.2)).interrogate() + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Should display value and asymmetric tolerance separated by
+ assert "2.0
tol=(0.1, 0.2)" in html + + +def test_agg_report_values_with_reference_column(): + """Test that VALUES column shows ref('column') for reference columns.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + ref_data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + + validation = ( + Validate(data, reference=ref_data).col_sum_eq(columns="a", value=ref("a")).interrogate() + ) + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Should display ref('a') for the reference column + assert "ref('a')" in html or "ref('a')" in html # HTML may escape quotes + + +def test_agg_report_values_implicit_reference(): + """Test that VALUES column shows ref('column') for implicit reference.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + ref_data = pl.DataFrame({"a": [1, 2, 3, 4, 5]}) + + # When value is omitted with reference data, it should default to ref('a') + validation = Validate(data, reference=ref_data).col_sum_eq(columns="a").interrogate() + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Should display ref('a') for the implicit reference column + assert "ref('a')" in html or "ref('a')" in html # HTML may escape quotes + + +def test_agg_report_multiple_steps_formatting(): + """Test that multiple aggregation steps all display correctly.""" + data = pl.DataFrame({"a": [1, 2, 3, 4, 5], "b": [10, 20, 30, 40, 50]}) + + validation = ( + Validate(data) + .col_sum_gt(columns="a", value=10) # No tolerance + .col_avg_eq(columns="b", value=30, tol=0.1) # Symmetric tolerance + .col_sd_le(columns="a", value=2.0, tol=(0.1, 0.2)) # Asymmetric tolerance + .interrogate() + ) + + report = validation.get_tabular_report() + html = report.as_raw_html() + + # Step 1: Just the value (no tolerance) + assert ">10<" in html + + # Step 2: Value with symmetric tolerance + assert "30
tol=0.1" in html + + # Step 3: Value with asymmetric tolerance + assert "2.0
tol=(0.1, 0.2)" in html