{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Referral completion and where the pathway breaks\n",
    "\n",
    "*Protection referrals, 2024 · R*\n",
    "\n",
    "Cassion · data-analysis.cassion.dev\n",
    "\n",
    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/thecassion/cassion-learning-platform/blob/main/apps/data-analysis/public/datasets/examples/protection-referrals-2024/referral-completion.r.en.ipynb)"
   ],
   "id": "cell-000"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Before any code\n",
    "\n",
    "Synthetic data modelling protection and GBV cases. No real person is described,\n",
    "and this file must never be used as a template for storing real case data — the\n",
    "safe version of that is a consent-governed case management system.\n",
    "\n",
    "The same decomposition as the Python example, in dplyr, with the contradictory\n",
    "and missing records handled explicitly rather than dropped silently.\n",
    "\n",
    "## Setup"
   ],
   "id": "cell-001"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#| message: false\n",
    "library(readr)\n",
    "library(dplyr)\n",
    "library(tidyr)\n",
    "\n",
    "URL <- paste0(\n",
    "  \"https://data-analysis.cassion.dev/datasets/files/\",\n",
    "  \"protection-referrals-2024.v1.csv\"\n",
    ")\n",
    "\n",
    "cases <- read_csv(URL, col_types = cols(\n",
    "  case_id  = col_character(),\n",
    "  .default = col_guess()\n",
    "))\n",
    "\n",
    "glimpse(cases)"
   ],
   "id": "cell-002"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Consent gates the denominator"
   ],
   "id": "cell-003"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cases |>\n",
    "  summarise(\n",
    "    cases            = n(),\n",
    "    consented        = sum(consent_to_refer),\n",
    "    consent_rate     = round(mean(consent_to_refer), 3)\n",
    "  )"
   ],
   "id": "cell-004"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "consenting <- cases |> filter(consent_to_refer)\n",
    "\n",
    "consenting |>\n",
    "  summarise(\n",
    "    denominator = n(),\n",
    "    completed   = sum(referral_accepted),\n",
    "    completion  = round(mean(referral_accepted), 3)\n",
    "  )"
   ],
   "id": "cell-005"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The completion denominator is cases that consented, not all cases. Counting a\n",
    "non-consenting case as a pathway failure both misstates performance and\n",
    "misrepresents a person's decision — the pathway did what it should when someone\n",
    "declined.\n",
    "\n",
    "## Handle the contradictions explicitly"
   ],
   "id": "cell-006"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cases |>\n",
    "  summarise(\n",
    "    time_but_not_accepted   = sum(!referral_accepted & !is.na(days_to_first_service)),\n",
    "    time_but_no_referral    = sum(!referral_made & !is.na(days_to_first_service)),\n",
    "    time_but_no_consent     = sum(!consent_to_refer & !is.na(days_to_first_service)),\n",
    "    accepted_but_no_time    = sum(referral_accepted & is.na(days_to_first_service))\n",
    "  ) |>\n",
    "  pivot_longer(everything(), names_to = \"contradiction\", values_to = \"cases\")"
   ],
   "id": "cell-007"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eleven records carry a service time with no accepted referral behind it, and six\n",
    "of those show no referral made at all. Forty accepted referrals have no time\n",
    "recorded, which means **the timeliness denominator is smaller than the completion\n",
    "denominator** — using one for both misstates both."
   ],
   "id": "cell-008"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cases <- cases |>\n",
    "  mutate(contradictory = !referral_accepted & !is.na(days_to_first_service))\n",
    "\n",
    "cat(\"flagged, not dropped:\", sum(cases$contradictory), \"\\n\")"
   ],
   "id": "cell-009"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Flagging rather than dropping matters here. In case management a contradictory\n",
    "record is an entry issue to send back to the caseworker, and deleting it destroys\n",
    "the only trace that the case existed.\n",
    "\n",
    "## Normalise disability before disaggregating"
   ],
   "id": "cell-010"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "count(cases, disability_reported)"
   ],
   "id": "cell-011"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cases <- cases |>\n",
    "  mutate(\n",
    "    disability = case_when(\n",
    "      tolower(trimws(disability_reported)) %in% c(\"true\", \"yes\") ~ TRUE,\n",
    "      tolower(trimws(disability_reported)) %in% c(\"false\", \"no\")  ~ FALSE,\n",
    "      TRUE ~ NA\n",
    "    )\n",
    "  )\n",
    "\n",
    "consenting <- cases |> filter(consent_to_refer)\n",
    "count(consenting, disability)"
   ],
   "id": "cell-012"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One area used `Yes` and `No`. Left alone the disaggregation fragments into four\n",
    "categories, two of them from that single area and too small to interpret.\n",
    "\n",
    "## Where the pathway breaks"
   ],
   "id": "cell-013"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "completion <- function(df, by) {\n",
    "  df |>\n",
    "    group_by(across(all_of(by))) |>\n",
    "    summarise(\n",
    "      cases      = n(),\n",
    "      completed  = sum(referral_accepted),\n",
    "      completion = round(mean(referral_accepted), 3),\n",
    "      .groups = \"drop\"\n",
    "    ) |>\n",
    "    arrange(completion)\n",
    "}\n",
    "\n",
    "completion(consenting, \"service_requested\")"
   ],
   "id": "cell-014"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "completion(consenting, \"admin2\")"
   ],
   "id": "cell-015"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Livelihood support completes at about 23% against health at about 62%. That is\n",
    "not caseworker performance — it is which services exist and have capacity. An\n",
    "analysis that stops at the overall 46% hides the whole finding."
   ],
   "id": "cell-016"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid <- consenting |>\n",
    "  group_by(admin2, service_requested) |>\n",
    "  summarise(cases = n(), completion = round(mean(referral_accepted), 2), .groups = \"drop\") |>\n",
    "  mutate(completion = if_else(cases >= 20, completion, NA_real_)) |>\n",
    "  select(-cases) |>\n",
    "  pivot_wider(names_from = service_requested, values_from = completion)\n",
    "\n",
    "grid"
   ],
   "id": "cell-017"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cells below twenty cases are blanked. A rate on eight cases is not a finding, and\n",
    "in protection work a small cell is a disclosure risk as well as a statistical one.\n",
    "\n",
    "## The equity finding"
   ],
   "id": "cell-018"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "by_disability <- consenting |>\n",
    "  filter(!is.na(disability)) |>\n",
    "  completion(\"disability\")\n",
    "\n",
    "by_disability"
   ],
   "id": "cell-019"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tbl <- table(\n",
    "  consenting$disability[!is.na(consenting$disability)],\n",
    "  consenting$referral_accepted[!is.na(consenting$disability)]\n",
    ")\n",
    "chisq.test(tbl)"
   ],
   "id": "cell-020"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cases where a disability was reported complete about seventeen points lower, and\n",
    "the difference is unlikely to be chance. What it does not tell you is *why* —\n",
    "whether services are physically inaccessible, whether pathways assume mobility\n",
    "some clients do not have, or something else. That is answered by asking\n",
    "caseworkers, not by this table.\n",
    "\n",
    "## Small cells are a protection risk"
   ],
   "id": "cell-021"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "consenting |>\n",
    "  count(admin2, case_category) |>\n",
    "  filter(n < 20)"
   ],
   "id": "cell-022"
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This check returns nothing here — every area-by-category cell clears twenty — and\n",
    "that empty result is the point of running it. A district cell of three GBV cases\n",
    "can identify a survivor to anyone who knows the area, so the check goes in the\n",
    "pipeline permanently rather than being run once. Set the threshold with the case\n",
    "management agency, not as a formatting choice.\n",
    "\n",
    "## What to report\n",
    "\n",
    "Completion on the consent-gated denominator, decomposed far enough to locate the\n",
    "failing node, contradictions flagged and counted, the disability gap stated\n",
    "plainly — and nothing at a granularity that could identify a person."
   ],
   "id": "cell-023"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "R",
   "language": "R",
   "name": "ir"
  },
  "language_info": {
   "name": "R",
   "file_extension": ".r"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
