diff --git a/privacypanda/__init__.py b/privacypanda/__init__.py
index 5e891fe..ab09290 100644
--- a/privacypanda/__init__.py
+++ b/privacypanda/__init__.py
@@ -1,4 +1,5 @@
 from .addresses import *
 from .anonymize import *
+from .report import *
 
 __version__ = "0.1.0dev"
diff --git a/privacypanda/report.py b/privacypanda/report.py
new file mode 100644
index 0000000..e8af3a3
--- /dev/null
+++ b/privacypanda/report.py
@@ -0,0 +1,66 @@
+"""
+Code for reporting the privacy of a dataframe
+"""
+from collections import defaultdict
+from typing import TYPE_CHECKING, List, Union
+
+from .addresses import check_addresses
+
+if TYPE_CHECKING:
+    import pandas
+
+
+__all__ = ["report_privacy"]
+
+
+class Report:
+    def __init__(self):
+        self._breaches = defaultdict(list)
+
+    def add_breach(self, columns: Union[List[str], str], breach: str):
+        """
+        Add columns which breach privacy to the dictionary of breaches
+
+        Parameters
+        ----------
+        columns : list of strings or a string
+            A single column or a list of columns which have breached privacy
+        breach : str
+            A type of breach
+
+        Returns
+        -------
+        None
+
+        Raises
+        ------
+        AssertionError
+            If breach not "address", "name", "email", "phone number"
+        """
+        breach = breach.lower()
+        valid_breaches = ("address", "name", "email", "phone number")
+        assert breach in valid_breaches, f"{breach} not in {valid_breaches}"
+
+        if isinstance(columns, str):
+            columns = [columns]
+
+        for column in columns:
+            self._breaches[column].append(breach)
+
+    def __str__(self):
+        report = ""
+        for column, breaches in self._breaches.items():
+            report += f"{column}: {breaches}\n"
+        return report
+
+
+def report_privacy(df: "pandas.DataFrame") -> Report:
+    report = Report()
+
+    checks = {"address": check_addresses}
+
+    for breach, check in checks.items():
+        columns = check(df)
+        report.add_breach(columns, breach)
+
+    return report

Create simple report
A user should be able to query a dataframe and see a list of privacy breaching columns and their reason for breaching privacy.

Basic functionality is to print a list of  `column: reason` pairs
diff --git a/tests/test_report.py b/tests/test_report.py
new file mode 100644
index 0000000..ced0754
--- /dev/null
+++ b/tests/test_report.py
@@ -0,0 +1,40 @@
+"""
+Test reporting functionality
+"""
+import pandas as pd
+import pytest
+
+import privacypanda as pp
+
+
+def test_can_report_addresses():
+    df = pd.DataFrame(
+        {
+            "col1": ["a", "b", "10 Downing Street"],
+            "col2": [1, 2, 3],
+            "col3": ["11 Downing Street", "b", "c"],
+        }
+    )
+
+    # Check correct breaches have been logged
+    report = pp.report_privacy(df)
+    expected_breaches = {"col1": ["address"], "col3": ["address"]}
+
+    assert report._breaches == expected_breaches
+
+    # Check string report
+    actual_string = str(report)
+    expected_string = "col1: ['address']\ncol3: ['address']\n"
+
+    assert actual_string == expected_string
+
+
+def test_report_can_accept_multiple_breaches_per_column():
+    report = pp.report.Report()
+
+    report.add_breach("col1", "address")
+    report.add_breach("col1", "phone number")
+
+    expected_breaches = {"col1": ["address", "phone number"]}
+
+    assert report._breaches == expected_breaches

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
exceptiongroup==1.2.2
iniconfig==2.1.0
numpy==2.0.2
packaging==24.2
pandas==2.2.3
pluggy==1.5.0
-e git+https://github.com/TTitcombe/PrivacyPanda.git@d828b176771e391ee6bda1c632f9237582a8dabe#egg=privacypanda
pytest==8.3.5
python-dateutil==2.9.0.post0
pytz==2025.2
six==1.17.0
tomli==2.2.1
tzdata==2025.2

name: PrivacyPanda
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - exceptiongroup==1.2.2
      - iniconfig==2.1.0
      - numpy==2.0.2
      - packaging==24.2
      - pandas==2.2.3
      - pluggy==1.5.0
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - six==1.17.0
      - tomli==2.2.1
      - tzdata==2025.2
prefix: /opt/conda/envs/PrivacyPanda

tests/test_report.py::test_can_report_addresses tests/test_report.py::test_report_can_accept_multiple_breaches_per_column
privacypanda/report.py privacypanda/__init__.py
privacypanda/report.py privacypanda/__init__.py