bolster.data_sources.justice.pbni_caseload ========================================== .. py:module:: bolster.data_sources.justice.pbni_caseload .. autoapi-nested-parse:: Probation Board for Northern Ireland (PBNI) Caseload Statistics. Provides access to PBNI's annual and quarterly caseload bulletins, covering the people under probation supervision in Northern Ireland: how many there are, what orders they are serving, their demographics, risk assessments, and the victims registered with the Victim Information Scheme. Two cadences are published: - **Annual** - a financial-year snapshot at 31 March, with five years of history and the widest breakdowns (offence classification, gender, directorate). - **Quarterly** - a snapshot at each quarter end, with roughly nine quarters of history and some extra flow measures (new caseload, reports completed). Data Source: PBNI publishes through GOV.UK, but the statistics themselves live in NISRA interactive data-visualisation pages at ``datavis.nisra.gov.uk/pbni/``. Each figure on those pages embeds its underlying CSV as a base64 data URI, so the published numbers are recovered exactly rather than scraped from charts. Publication URLs are discovered at runtime through the GOV.UK Search and Content APIs, so new releases are picked up without a code change. Update Frequency: Quarterly, with an additional annual bulletin each spring Geographic Coverage: Northern Ireland Reference Period: Annual 2022 - present; quarterly 2024 - present .. note:: The published CSVs are UTF-16LE encoded but use bare single-byte ``\r\n`` line terminators, which desynchronises any standard UTF-16 decoder after the first row. :func:`_decode_csv` works around this by stripping null bytes. .. rubric:: Example >>> from bolster.data_sources.justice import pbni_caseload >>> df = pbni_caseload.get_annual_caseload() >>> {"date", "caseload", "service_users"}.issubset(df.columns) True Attributes ---------- .. autoapisummary:: bolster.data_sources.justice.pbni_caseload.logger bolster.data_sources.justice.pbni_caseload.SEARCH_API_URL bolster.data_sources.justice.pbni_caseload.CONTENT_API_URL bolster.data_sources.justice.pbni_caseload.DATAVIS_HOST bolster.data_sources.justice.pbni_caseload.FREQUENCIES Exceptions ---------- .. autoapisummary:: bolster.data_sources.justice.pbni_caseload.PBNIDataError bolster.data_sources.justice.pbni_caseload.PBNIDataNotFoundError Functions --------- .. autoapisummary:: bolster.data_sources.justice.pbni_caseload.find_latest_publication bolster.data_sources.justice.pbni_caseload.list_dimensions bolster.data_sources.justice.pbni_caseload.get_latest_data bolster.data_sources.justice.pbni_caseload.get_annual_caseload bolster.data_sources.justice.pbni_caseload.get_quarterly_caseload bolster.data_sources.justice.pbni_caseload.validate_data Module Contents --------------- .. py:data:: logger .. py:data:: SEARCH_API_URL :value: 'https://www.gov.uk/api/search.json' .. py:data:: CONTENT_API_URL :value: 'https://www.gov.uk/api/content' .. py:data:: DATAVIS_HOST :value: 'datavis.nisra.gov.uk' .. py:data:: FREQUENCIES :value: ('annual', 'quarterly') .. py:exception:: PBNIDataError Bases: :py:obj:`Exception` Base exception for PBNI caseload data errors. Initialize self. See help(type(self)) for accurate signature. .. py:exception:: PBNIDataNotFoundError Bases: :py:obj:`PBNIDataError` Raised when a publication or figure cannot be located. Initialize self. See help(type(self)) for accurate signature. .. py:function:: find_latest_publication(frequency = 'annual') Find the datavis URL of the most recent PBNI caseload bulletin. :param frequency: Either ``"annual"`` or ``"quarterly"``. :returns: Absolute URL of the NISRA data-visualisation page holding the data. :raises ValueError: If ``frequency`` is not a recognised cadence. :raises PBNIDataNotFoundError: If no matching publication is found on GOV.UK. .. rubric:: Example >>> find_latest_publication("annual").startswith("https://datavis.nisra.gov.uk/pbni/") True .. py:function:: list_dimensions(frequency = 'annual') List the dimensions available for a publication cadence. :param frequency: Either ``"annual"`` or ``"quarterly"``. :returns: Sorted dimension names accepted by :func:`get_latest_data`. .. rubric:: Example >>> "caseload" in list_dimensions("annual") True .. py:function:: get_latest_data(dimension = 'caseload', frequency = 'annual', force_refresh = False) Retrieve the latest PBNI caseload statistics. :param dimension: Which breakdown to return, or ``"all"`` for every dimension. See :func:`list_dimensions` for the options. :param frequency: Either ``"annual"`` or ``"quarterly"``. :param force_refresh: Bypass the local cache and re-download the bulletin. :returns: A DataFrame for a single dimension, or a mapping of dimension name to DataFrame when ``dimension="all"``. :raises ValueError: If ``frequency`` or ``dimension`` is not recognised. .. rubric:: Example >>> df = get_latest_data("order_type", frequency="annual") >>> "Probation Order" in set(df["order_type"]) True .. py:function:: get_annual_caseload(force_refresh = False) Retrieve the annual caseload and service-user totals at each 31 March. :param force_refresh: Bypass the local cache and re-download the bulletin. :returns: DataFrame with ``date``, ``caseload`` and ``service_users`` columns. .. rubric:: Example >>> df = get_annual_caseload() >>> (df["caseload"] >= df["service_users"]).all() True .. py:function:: get_quarterly_caseload(force_refresh = False) Retrieve the quarterly caseload and service-user totals at each quarter end. :param force_refresh: Bypass the local cache and re-download the bulletin. :returns: DataFrame with ``date``, ``caseload`` and ``service_users`` columns. .. rubric:: Example >>> df = get_quarterly_caseload() >>> len(df) >= 8 True .. py:function:: validate_data(df) Check that a caseload frame is structurally sound. :param df: A frame returned by :func:`get_annual_caseload` or :func:`get_quarterly_caseload`. :returns: True if the frame passes every check. :raises PBNIDataError: If the frame is empty, missing columns, or holds non-positive counts. .. rubric:: Example >>> validate_data(get_annual_caseload()) True