{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "efd57687",
   "metadata": {},
   "source": [
    "# BreaKHis Split Audit and Patient Leakage\n",
    "\n",
    "Before doing any new modelling I wanted to check something that had started to bother me about the way I first split the images. Once I realised the filenames carry patient IDs, I couldn't really trust an image-level split without checking who was leaking across subsets. So this notebook is where I pull the patient codes out, measure the overlap directly, and rebuild the split so each patient lives in only one of train, validation, or test.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Notebook Purpose\n",
    "\n",
    "This notebook proves why the BreaKHis workflow must split by patient rather than by image. Because each patient can contribute multiple images, a naive image-level split can leak patient information into train, validation, and test subsets.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Why This Matters\n",
    "\n",
    "Patient leakage can make a model appear stronger than it really is. For a medical imaging dissertation, this is a critical methodological risk. The corrected patient-level split is therefore not a minor implementation detail; it is what makes the image-model evaluation credible.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Build Both Split Strategies\n",
    "\n",
    "This setup cell creates two competing splits: the initial image-level split and the corrected patient-level split. Comparing them side by side makes the leakage problem measurable rather than theoretical.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "925f3154",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-04-19T20:10:54.984321Z",
     "iopub.status.busy": "2026-04-19T20:10:54.983872Z",
     "iopub.status.idle": "2026-04-19T20:10:56.714501Z",
     "shell.execute_reply": "2026-04-19T20:10:56.713890Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Project root: /Users/sergeysotskiy/Documents/UNI/year 3/Dissertation/dissertation_project\n",
      "Outputs: /Users/sergeysotskiy/Documents/UNI/year 3/Dissertation/dissertation_project/outputs\n"
     ]
    }
   ],
   "source": [
    "from __future__ import annotations\n",
    "\n",
    "import json\n",
    "import random\n",
    "import sys\n",
    "from pathlib import Path\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "SEED = 42\n",
    "random.seed(SEED)\n",
    "np.random.seed(SEED)\n",
    "plt.style.use('seaborn-v0_8-whitegrid')\n",
    "\n",
    "CWD = Path.cwd().resolve()\n",
    "if (CWD / 'src').exists() and (CWD / 'data').exists():\n",
    "    PROJECT_ROOT = CWD\n",
    "elif (CWD.parent / 'src').exists() and (CWD.parent / 'data').exists():\n",
    "    PROJECT_ROOT = CWD.parent\n",
    "elif (CWD.parent.parent / 'src').exists() and (CWD.parent.parent / 'data').exists():\n",
    "    PROJECT_ROOT = CWD.parent.parent\n",
    "else:\n",
    "    raise RuntimeError(f'Could not resolve dissertation_project root from {CWD}')\n",
    "\n",
    "REPO_ROOT = PROJECT_ROOT.parent\n",
    "OUTPUTS = PROJECT_ROOT / 'outputs'\n",
    "FIGURES = OUTPUTS / 'figures'\n",
    "METRICS = OUTPUTS / 'metrics'\n",
    "REPORTS = OUTPUTS / 'reports'\n",
    "MODELS = PROJECT_ROOT / 'models'\n",
    "DATA_ROOT = PROJECT_ROOT / 'data' / 'dataset_cancer_v1' / 'dataset_cancer_v1'\n",
    "WISCONSIN_ROOT = PROJECT_ROOT / 'notebook_Wisconsin'\n",
    "\n",
    "for path in [FIGURES, METRICS, REPORTS]:\n",
    "    path.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "if str(PROJECT_ROOT) not in sys.path:\n",
    "    sys.path.append(str(PROJECT_ROOT))\n",
    "\n",
    "print('Project root:', PROJECT_ROOT)\n",
    "print('Outputs:', OUTPUTS)\n",
    "\n",
    "from IPython.display import display\n",
    "\n",
    "from src.breakhis import (\n",
    "    build_binary_records,\n",
    "    patient_level_split,\n",
    "    patient_overlap_examples,\n",
    "    patient_overlap_report,\n",
    "    random_image_level_split,\n",
    "    split_label_summary,\n",
    "    split_summary,\n",
    ")\n",
    "\n",
    "df = build_binary_records(DATA_ROOT)\n",
    "image_level_split = random_image_level_split(df, random_state=SEED)\n",
    "corrected_split = patient_level_split(df, random_state=SEED)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Quantify Patient Overlap\n",
    "\n",
    "This cell calculates how many patients appear in more than one subset for each split strategy. The expected outcome is that image-level splitting has overlap, while patient-level splitting has zero overlap.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "87181467",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-04-19T20:10:56.716518Z",
     "iopub.status.busy": "2026-04-19T20:10:56.716340Z",
     "iopub.status.idle": "2026-04-19T20:10:56.726073Z",
     "shell.execute_reply": "2026-04-19T20:10:56.725654Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>split_type</th>\n",
       "      <th>subset_pair</th>\n",
       "      <th>overlap_count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>image_level_initial_attempt</td>\n",
       "      <td>train_val_overlap</td>\n",
       "      <td>82</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>image_level_initial_attempt</td>\n",
       "      <td>train_test_overlap</td>\n",
       "      <td>82</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>image_level_initial_attempt</td>\n",
       "      <td>val_test_overlap</td>\n",
       "      <td>82</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>corrected_patient_level</td>\n",
       "      <td>train_val_overlap</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>corrected_patient_level</td>\n",
       "      <td>train_test_overlap</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>corrected_patient_level</td>\n",
       "      <td>val_test_overlap</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                split_type         subset_pair  overlap_count\n",
       "0       image_level_initial_attempt   train_val_overlap             82\n",
       "1       image_level_initial_attempt  train_test_overlap             82\n",
       "2       image_level_initial_attempt    val_test_overlap             82\n",
       "3  corrected_patient_level   train_val_overlap              0\n",
       "4  corrected_patient_level  train_test_overlap              0\n",
       "5  corrected_patient_level    val_test_overlap              0"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "image_level_overlap = patient_overlap_report(image_level_split)\n",
    "corrected_overlap = patient_overlap_report(corrected_split)\n",
    "overlap_df = pd.DataFrame(\n",
    "    [\n",
    "        {'split_type': 'image_level_initial_attempt', 'subset_pair': key, 'overlap_count': value}\n",
    "        for key, value in image_level_overlap.items()\n",
    "    ]\n",
    "    + [\n",
    "        {'split_type': 'corrected_patient_level', 'subset_pair': key, 'overlap_count': value}\n",
    "        for key, value in corrected_overlap.items()\n",
    "    ]\n",
    ")\n",
    "overlap_df.to_csv(REPORTS / 'breakhis_overlap_audit.csv', index=False)\n",
    "overlap_df\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Inspect Concrete Leakage Examples\n",
    "\n",
    "This cell saves examples of leaked patients from the image-level split. Concrete examples are useful in a dissertation because they make the leakage issue easier to explain and defend.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c6d5e36e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-04-19T20:10:56.727510Z",
     "iopub.status.busy": "2026-04-19T20:10:56.727414Z",
     "iopub.status.idle": "2026-04-19T20:10:56.733693Z",
     "shell.execute_reply": "2026-04-19T20:10:56.733244Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>overlap</th>\n",
       "      <th>patient_id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_A-14-22549AB</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_A-14-22549CD</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_A-14-22549G</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_A-14-29960CD</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-14134</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-14134E</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-21998CD</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>7</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-21998EF</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>8</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-23060AB</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>9</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-23060CD</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>10</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-23222AB</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>11</th>\n",
       "      <td>train_val</td>\n",
       "      <td>SOB_B_F-14-25197</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "      overlap          patient_id\n",
       "0   train_val  SOB_B_A-14-22549AB\n",
       "1   train_val  SOB_B_A-14-22549CD\n",
       "2   train_val   SOB_B_A-14-22549G\n",
       "3   train_val  SOB_B_A-14-29960CD\n",
       "4   train_val    SOB_B_F-14-14134\n",
       "5   train_val   SOB_B_F-14-14134E\n",
       "6   train_val  SOB_B_F-14-21998CD\n",
       "7   train_val  SOB_B_F-14-21998EF\n",
       "8   train_val  SOB_B_F-14-23060AB\n",
       "9   train_val  SOB_B_F-14-23060CD\n",
       "10  train_val  SOB_B_F-14-23222AB\n",
       "11  train_val    SOB_B_F-14-25197"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "image_level_examples = patient_overlap_examples(image_level_split, limit=12)\n",
    "image_level_examples.to_csv(REPORTS / 'breakhis_image_level_overlap_examples.csv', index=False)\n",
    "image_level_examples.head(12)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Save Corrected Split Summaries\n",
    "\n",
    "This cell records both split-level and label-level summaries. It also saves the corrected train, validation, and test CSV files that later notebooks reuse, ensuring that downstream modelling uses the leakage-safe protocol.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "0da8ee27",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-04-19T20:10:56.734869Z",
     "iopub.status.busy": "2026-04-19T20:10:56.734794Z",
     "iopub.status.idle": "2026-04-19T20:10:56.775294Z",
     "shell.execute_reply": "2026-04-19T20:10:56.774823Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>subset</th>\n",
       "      <th>images</th>\n",
       "      <th>patients</th>\n",
       "      <th>benign_images</th>\n",
       "      <th>malignant_images</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>train</td>\n",
       "      <td>5536</td>\n",
       "      <td>82</td>\n",
       "      <td>1736</td>\n",
       "      <td>3800</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>val</td>\n",
       "      <td>1186</td>\n",
       "      <td>82</td>\n",
       "      <td>372</td>\n",
       "      <td>814</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>test</td>\n",
       "      <td>1187</td>\n",
       "      <td>82</td>\n",
       "      <td>372</td>\n",
       "      <td>815</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  subset  images  patients  benign_images  malignant_images\n",
       "0  train    5536        82           1736              3800\n",
       "1    val    1186        82            372               814\n",
       "2   test    1187        82            372               815"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>subset</th>\n",
       "      <th>images</th>\n",
       "      <th>patients</th>\n",
       "      <th>benign_images</th>\n",
       "      <th>malignant_images</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>train</td>\n",
       "      <td>5403</td>\n",
       "      <td>57</td>\n",
       "      <td>1597</td>\n",
       "      <td>3806</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>val</td>\n",
       "      <td>1171</td>\n",
       "      <td>12</td>\n",
       "      <td>258</td>\n",
       "      <td>913</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>test</td>\n",
       "      <td>1335</td>\n",
       "      <td>13</td>\n",
       "      <td>625</td>\n",
       "      <td>710</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "  subset  images  patients  benign_images  malignant_images\n",
       "0  train    5403        57           1597              3806\n",
       "1    val    1171        12            258               913\n",
       "2   test    1335        13            625               710"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "image_level_summary = split_summary(image_level_split)\n",
    "corrected_summary = split_summary(corrected_split)\n",
    "image_level_label_summary = split_label_summary(image_level_split)\n",
    "corrected_label_summary = split_label_summary(corrected_split)\n",
    "\n",
    "image_level_summary.to_csv(REPORTS / 'breakhis_image_level_split_summary.csv', index=False)\n",
    "corrected_summary.to_csv(REPORTS / 'breakhis_patient_split_summary.csv', index=False)\n",
    "image_level_label_summary.to_csv(REPORTS / 'breakhis_image_level_split_label_summary.csv', index=False)\n",
    "corrected_label_summary.to_csv(REPORTS / 'breakhis_patient_split_label_summary.csv', index=False)\n",
    "corrected_split.train.to_csv(REPORTS / 'breakhis_patient_train_split.csv', index=False)\n",
    "corrected_split.val.to_csv(REPORTS / 'breakhis_patient_val_split.csv', index=False)\n",
    "corrected_split.test.to_csv(REPORTS / 'breakhis_patient_test_split.csv', index=False)\n",
    "\n",
    "display(image_level_summary)\n",
    "display(corrected_summary)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## Visual Leakage Audit\n",
    "\n",
    "This plot summarizes the overlap counts visually. It is designed as a methods figure: it shows the initial problem and the corrected solution in one place.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6bbf9099",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-04-19T20:10:56.776596Z",
     "iopub.status.busy": "2026-04-19T20:10:56.776515Z",
     "iopub.status.idle": "2026-04-19T20:10:56.906971Z",
     "shell.execute_reply": "2026-04-19T20:10:56.906356Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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k08+abGxse79N998071/8OBBa926tf3666920003ub8TEhJs+PDhNmvWLPdZpV/fPe2009Ldzb///rsNGDDAFixYYMWLF3fLu++++9y2XXXVVfbggw9amzZt3Gd9Pp977ZFHHrFWrVrZzz//bM8++6ytX7/eypYta927d7cmTf7/Ps3GGGMBAAAQxXIvmWJ29LAdvbKHHa33H4v5e6vlXjb93zePxlue78eb77QydvTqRy3xnJoukPDk2vqzxa6fZ0dr3mwJ1/WxYxc1ttxrPrOYvf+O34hdPdtity60o7U7uEL2n3/+af369bNrrrnGDh8+bD/88IN/WbNnz7ZmzZodF1Sk5oUXXnCF8Pfee89WrVrlAoHLL7/cPvzwQ8ufP7+NGjXKfe6nn36yQYMG2cMPP2xz5sxxadBn5s79dxtmzpxpo0ePtieeeMLef/99F3wsXLjQvx4tZ+XKlTZhwgQX2Bw4cMAeeOCBdNOnQEHBgAKKGTNmuIBp1qxZ9uqrr1quXLmsadOm9sUXX/g/v3TpUtu7d6/bL7t377YuXbq4QETfueeee9yYCgUb2V1EAwtFetqxtWrVctGpIkqPDqJ27dpZ9erVXbSnHx0AAAAhOLDHcu1YacfqdDRf0dLmK1bWjtW82XJtWeiCilzbl7pWjGPVbzJf4ZKWVL6BJZ1d3f91X4HT7FjtDuYrWcG1fiSdd7n58ha2mH07/23N2PyjHavc3HylKtk555zjggq1JKjloFGjRq6wL/Hx8fbNN9/Y9dcHN1ZEhW4FElWqVLFLL73ULfOWW25x/99www22ceNG97kCBQq4lpHGjRu79atAX7lyZdfSIQpM7rjjDhfQ6LvDhg2zfPny+dM0adIkF4xUq1bNtZqo9ULBilo10qKAaceOHa4l5fzzz3ctLb169XLBiWg71VqjQMVrrVGLRaFChezdd99123bbbbe51gq1YLRv397eeuutbH9oR7QrlJqISpcubdOnT3dNQY8++qidffbZdsUVV1jnzp2tZcuWrtlLTWEKQBT56QACAABA+nLt32Ux5rO42QOSva7XYhR07NthSaedbRbzf3XNCj5idqz49+8SF5r9tcVif/mvxez/w3Lt/c1ijuw38yWZJRy0mISDrvuU54ILLvDPnNSiRQvXrah///72v//9z0qWLOkChWCUKVPG/7cCAZUPA58f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/vnnW/ny5V3NvWrwFaioG5PGEaTl0KFDrkZfXYrU5Undpjp27OhaE7Ru1fJrpiaN4fAGX3vBgWr6lYaUBXGlQ4V5zbK0fPly14VKYyu0LgVH6cmfP78/yFG3prRoDIgGqGvgt8Z2KMhSEKE0eBRMvPHGG24feS09CiLatWtn/fv3d+Mu1q9f71potP/POeccO1UILAAAAKJJrlg7duldFnN4n5seNvfyj+xY1Rss6czKbtzF0cs7W8zBPZbna7033RIvuNISL/p3ytKgeMvQlLZa/pIPLPH8+nbDDTe4tzVIWoV7TSOr19TKoFr3fPnyucHHqk1v2bKle/3uu++2tm3b2tNPP+3GQGiq2ddee80fpKiF4fTTT3djBzTuQEFLWtTioS5OWqYGQqtgrpmVFFjob7WqqGA+dOhQu++++9xnvVYQFcwVcGh625TUOqECugIhFf5VoA8cB5GWihUr2hVXXOG2wbuz94k0b97cjTkZPXq0GwCv7mCvvPKKlStXzv8ZDexWoKbPBtKUuQrE1CKjQePqFqbxGGrJOFVifFFw1w5FjDooKlWqdFy/slOpXO9PIrbuaDat3ZluTuhTmTEAD/k6c5CvEUnk6+jM17qPhaZqVesHIlPOpsUCAAAAQNgILAAAAACEjcACAAAA2V7r1q3pBhVhBBYAAAAAwkZgAQAAACBsBBYAAAAAwkZgAQAAACBsBBYAAAAAwkZgAQAAACBsBBYAAAAAwkZgAQAAACD7BhbTp0+3ihUrHve46KKL3PurVq2ydu3aWfXq1a1Nmza2cuXKSCUVAAAAQFYNLJo3b27ffvut//G///3PypYta506dbJDhw5Z586drU6dOi4AqVmzpnXp0sW9DgAAACDriVhgkS9fPitRooT/MXPmTPP5fPboo4/ap59+annz5rXHH3/cypcvb3379rWCBQvanDlzIpVcAAAAAFl9jMXevXttwoQJ9sgjj1hcXJwtW7bMateubTExMe59/V+rVi1bunRppJMKAAAAIBW5LQuYPHmylSxZ0po2beqe79692y644IJknylevLitW7cuzeUkJia6B6IPvysQfcjXQPQhX+fs3zTigYW6P02dOtXuuece/2vx8fGu5SKQnickJKS5rLVr12ZaOhFZK1as4CcAogz5Gog+5OucLXdWOAB37dpl119/vf81ja9IGUToucZlpKVChQpWoEABi5ipjAHJLFWrVrXY2NhMWz5wQuTrTEO+RsSQrzMN+Tr6aPKkYCvvIx5YzJ8/383+VLRoUf9rpUqVsj179iT7nJ6ru1RaVPCk8Bmd+G2B6EO+BqIP+Tr6hFK2jvjg7eXLl7uB2YF074olS5a4blKi/xcvXuxeBwAAAJD1RDyw0IDslAO1NYh73759NnjwYFu/fr37X+MumjVrFrF0AgAAAMjCgYW6OBUpUiTZa4UKFbJx48bZokWLrHXr1m762fHjx0d2/AQAAACArDvGQl2hUlOtWjWbMWPGKU8PAAAAgGzYYgEAAAAg+yOwAAAAABA2AgsAAAAAYSOwAAAAABA2AgsAAAAAYSOwAAAAABA2AgsAAAAAYSOwAAAAABA2AgsAAAAAYSOwAAAAABA2AgsAAAAAYSOwAAAAABA2AgsAAAAAYSOwAAAAABA2AgsAAAAABBYAAAAAIo8WCwAAAABhI7AAAAAAEDYCCwAAAABhI7AAAAAAEDYCCwAAAABhI7AAAAAAEDYCCwAAAABhI7AAAAAAEDYCCwAAAABhI7AAAAAAkL0Di4SEBBswYIBdcskldvnll9uoUaPM5/O591atWmXt2rWz6tWrW5s2bWzlypWRTCoAAACArBpYDBo0yL7//nt77bXXbOTIkfbBBx/Y+++/b4cOHbLOnTtbnTp1bPr06VazZk3r0qWLex0AAABA1pM7Uiveu3evTZs2zd544w2rVq2ae+2uu+6yZcuWWe7cuS1v3rz2+OOPW0xMjPXt29fmzZtnc+bMsdatW0cqyQAAAACyWovFokWLrFChQla3bl3/a2qlGDJkiAsuateu7YIK0f+1atWypUuXRiq5AAAAALJiYLFt2zY7++yz7aOPPrKmTZvaNddcY2PGjLGkpCTbvXu3lSxZMtnnixcvbjt37oxUcgEAAABkxa5QGi+xZcsWmzJlimulUDDx9NNPW/78+S0+Pt7i4uKSfV7PNdg7LYmJie6B6MPvCkQf8jUQfcjXOfs3jVhgoXEUBw4ccIO21XIhO3bssMmTJ1vZsmWPCyL0PF++fGkuc+3atZmaZkTOihUr2P1AlCFfA9GHfJ2zRSywKFGihBug7QUVct5559nvv//uxl3s2bMn2ef1PGX3qJQqVKhgBQoUsIiZOidy645yVatWtdjY2EgnAzkR+TrTkK8RMeTrTEO+jj7qZRRs5X3EAgvdn+LIkSO2adMmF1DIxo0bXaCh9yZMmODuaaGB2/p/8eLF1rVr1zSXqYInhc/oxG8LRB/yNRB9yNfRJ5SydcQGb59//vnWsGFD69Onj61Zs8bmz59v48ePt1tuucUN5t63b58NHjzY1q9f7/7XuItmzZpFKrkAAAAAsuoN8p577jk799xzXTDRq1cvu/XWW+32229309COGzfOTUmr+1Zo+lkFHRHt5gQAAAAg63WFksKFC9vw4cNTfU83zZsxY8YpTxMAAACAbNZiAQAAACA6EFgAAAAACBuBBQAAAICwEVgAAAAACBuBBQAAAICwEVgAAAAACBuBBQAAAICwEVgAAAAACBuBBQAAAICwEVgAAAAACBuBBQAAAIBTH1gcO3bMJk+ebDt27HDPX3zxRbv++uvtscces71794afIgAAAADRH1gMHTrUxo4da/v27bMvv/zSJkyYYK1atbLff//dBg4cmDmpBAAAAJCl5Q71C59++qkLLC666CIXVNSvX986d+5sjRo1sg4dOmROKgEAAABEV4tFfHy8FS9e3HWJmjdvngsoJCkpyXLnDjlOAQAAABAFQo4EatWqZSNGjLBChQq5IOPaa6+1NWvWuG5Ql156aeakEgAAAEB0tVgMGjTItVb88ssvNmTIENd6MXv2bPd/v379MieVAAAAAKKrxWL79u1uJqg8efL4X3vooYcsISHBdY1SCwYAAACAnCXkFotOnTrZ/v37j3t9/fr19vDDD2dUugAAAABEW4vFe++9Z88884zFxMSYz+ezK664ItXPXX755RmdPgAAAADRElh07NjRLrzwQjfz0x133GGjR4+2okWL+t9XwJE/f36rUKFCZqYVAAAAQHYfY3HJJZe4/+fOnWulS5d2wQQAAAAAhBRYeEqWLGkffvihrVixws0Opa5RgTRTFAAAAICcJeTB23379rXBgwfb33//fVxQAQAAACBnCrnF4osvvrAxY8accAA3AAAAgJwn5BaLwoULW6lSpTInNQAAAAByRmBx3333ua5QGzZscGMsAAAAACDkrlATJkywP/74w1q0aJHq+6tXrw6pW1X37t2TvdakSRM3ne2qVausX79+tnbtWrvgggtswIABVqVKFX4xAAAAIBoCi6FDh2bYynW37kaNGtnAgQP9r+XNm9cOHTpknTt3tpYtW7r1TZ482bp06eICkQIFCmTY+gEAAABEKLCoW7eu+//AgQO2detW15qQkJBghQoVCnnl6k6lm+qVKFEi2euazlYBxuOPP+7ul6GZqObNm2dz5syx1q1bh7weAAAAAFlsjIWCiCeffNIFGG3btrVdu3ZZ79697e6777Z//vkn5MCiXLlyx72+bNkyq127tv8mfPq/Vq1atnTp0lCTCwAAACArtlgMHz7cdWGaMWOGdejQwb3Wo0cP69Onjw0aNMhGjBgR1HJ0D4xNmzbZt99+a+PGjbPExERr2rSp9ezZ03bv3u1aQgIVL17c1q1bl+YytQw9EH34XYHoQ74Gog/5Omf/piEHFp9//rm7j0XFihX9r+lvjZO46667gl7Ojh07LD4+3uLi4uyFF16w7du3u8Dk8OHD/tcD6blaS9Kigd6ITrrTO4DoQr4Gog/5OmcLObA4ePCg5c+f/7jXk5KSQopozj77bPvxxx+taNGirqtTpUqV3DIee+wx180qZRCh5/ny5UtzmRqvEdHB3VPnRG7dUa5q1aoWGxsb6WQgJyJfZxryNSKGfJ1pyNfRR5MqBVt5H3JgcfXVV9vzzz9vw4YN87+2bds219pw1VVXhbSs0047Ldnz8uXL25EjR9xg7j179iR7T89LliyZ5vJU8KTwGZ34bYHoQ74Gog/5OvqEUrYOefD2008/bbly5XKtCuqy1KZNG2vcuLEVKVLEnnrqqaCXM3/+fKtXr55bRuA9MBRsaOD2kiVL3DgM0f+LFy+26tWrh5pcAAAAAKdAyC0WhQsXtpdeesm1Unh33z7vvPNca0Moatas6aaU1QxT3bp1c8vTwPB77rnHDeIeOXKku8O3BohPmTLFBSDNmjULNbkAAAAAToFcwQ609loP9LceahbRmIbKlSu7MRfe68HSfS9ee+01++uvv1yrh+5V0b59exdY6D3NFLVo0SJ33wpNPzt+/HhujgcAAABk5xYLjav47rvv3JSv+tu7v0QgBR56Xd2ZgnXhhRfaG2+8kep71apVc1PaAgAAAIiSwGLu3Ll2+umn+/8GAAAAgJC7QmlqWA3YFt0IT+Ms9FrgQ92hdKM8AAAAADlPUC0W8+bNs+XLl7u/Fy5caK+++upx4x22bNliv/32W+akEgAAAED2Dyw069PEiRPdOApv6tc8efL439fYCgUamsUJAAAAQM4TVGBRpkwZe/vtt/1doTSDk2ZuAgAAAICgA4tAQ4YMcfeu2LVrlyUmJrrX1IqRkJDgZoRq3rw5exYAAADIYUIOLDQrlG5qt3fv3uPeK1GiBIEFAAAAkAMFNStUoOeee86uu+46++STT6xIkSLurtgazK2ZoR588MHMSSUAAACA6Gqx2LZtm7sr9rnnnmtVqlSx3bt327XXXuumox0+fLi7UzYAAACAnCXkFgu1UsTHx/tni1qzZo37+/zzz7ft27dnfAoBAAAARF9gcdVVV9mAAQNs/fr1Vq9ePfv444/tl19+sffff99KliyZOakEAAAAEF2BhaaaLVu2rK1cudJ1gapevbq1bdvW3n33XevVq1fmpBIAAABAdI2x0P0rNOVs4GDu/v37W968eZPdNA8AAABAzhFyYCHff/+96/q0ceNGd9ftihUr2q233mo1atTI+BQCAAAAiL6uUFOnTrXOnTtb/vz5rX379tamTRv3eqdOnezzzz/PjDQCAAAAiLYWi1deecUN3vYCCs8ll1xiI0eOtMaNG2dk+gAAAABEY4uF7ritAdsp1alTx/7444+MShcAAACAaA4sNJZi2LBh9vfff/tf030tdPftjh07ZnT6AAAAAERjV6hFixbZ8uXLrWHDhu7u25oJasuWLXbw4EErXbq0zZkzx//ZuXPnZnR6AQAAAERDYNGuXTv3AAAAAICTDixuuummUL8CAAAAIMqFPMYCAAAAAFIisAAAAAAQNgILAAAAAGEjsAAAAABwagZvX3311RYTExPUApliFgAAAMh5ggosevTo4f9769at9tZbb9ktt9xiVatWdfexWLVqlU2aNMnuuOOOk05I586drVixYjZ06FD3XMvs16+frV271i644AIbMGCAValS5aSXDwAAACDCgUXgFLOtW7e2wYMHW7NmzfyvXXPNNVapUiV74YUX7P777w85EZ988ol98803/vUcOnTIBRotW7Z0gcbkyZOtS5cu9sUXX1iBAgVCXj4AAACALDbGYtOmTVahQoXjXi9Tpoz99ttvISdg7969Nnz4cNf64fn0008tb9689vjjj1v58uWtb9++VrBgwWR39QYAAACQjQOL2rVr27PPPmu7du3yv7Zt2zYbNGiQNWjQIOQEDBs2zFq1auW6O3mWLVvm1uON69D/tWrVsqVLl4a8fAAAAABZ8M7bCip69uxpDRs2tKJFi5rP57N9+/bZZZddZgMHDgxpWQsWLLCff/7ZZs2aZf379/e/vnv37mSBhhQvXtzWrVuX5vISExPdA9GH3xWIPuRrIPqQr3P2bxpyYFGyZEmbMmWKrV+/3j3kwgsvdF2WQnHkyBE3OPvpp5+2fPnyJXsvPj7e4uLikr2m5wkJCWkuUwO9EZ1WrFgR6SQAyGDkayD6kK9ztpADCy9y2b59u+3cudMN5ta4i/3791vhwoWDXsbLL7/sZnlKrfuUxlekDCL0PGUAkpLGfkR0cPdUxoBkFo3BiY2NzbTlAydEvs405GtEDPk605Cvo48mVQq28j7kwOL333+3u+66y/755x/30IxQEydOtCVLlthrr71mFStWDHomqD179ljNmjXdcy+Q+Oyzz6xFixbuvUB6rtaStKjgSeEzOvHbAtGHfA1EH/J19AmlbB3y4O1nnnnG6tSpY/Pnz/d3Vxo1apRdfvnlbgB3sN555x03tuKjjz5yD92ETw/9Xb16dReoaPyG6P/Fixe71wEAAABkPSG3WGiw9QcffJAsetFN8nT/isD7XaTn7LPPTvZc08lK2bJl3UDtkSNHuvtldOjQwY3p0LiLwHtnAAAAAMg6Qm6x0DiHP//887jXNc6iUKFCGZIoLWfcuHG2aNEiN4ZD08+OHz+em+MBAAAA0dJioRYEzeSkm9d5AcVPP/1kzz//vLVr1+6kE6I7bAeqVq2azZgx46SXBwAAACALBxbdunWzIkWKuPtOqHtS586dXdelO++80+6+++7MSSUAAACA6Aos/vvf/1rLli3t9ttvd9NPaerZUKaZBQAAABB9Qh5jMWDAAPvrr7/c37pnBEEFAAAAgJADi3r16rlWi/Tugg0AAAAg5wi5K5RmhBo7dqy9+uqrVqxYMXeX7EBz587NyPQBAAAAiMbA4uabb3YPAAAAADjpwCKtm+AdPXo01MUBAAAAyImBxZ49e9zN69avX+9mhBKfz+eCig0bNtjChQszI50AAAAAomnw9hNPPGHz58+3qlWr2uLFi6169epurMXy5cutR48emZNKAAAAANHVYqEWiddff91q1qxp3333nTVs2NBq165t48ePt3nz5lmnTp0yJ6UAAAAAoqfFQt2eSpUq5f6+4IILbNWqVe7vZs2a2YoVKzI+hQAAAACiL7CoXLmyffzxx+7vSpUquVYL2b59e8anDgAAAEB0doV65JFHrGvXrpY/f35r1aqVTZw40Vq2bGk7duywG264IXNSCQAAACC6AguNp/j666/t8OHDdvrpp9u0adPsyy+/tNNOO811hwIAAACQ84QcWEihQoXcQzTe4tZbb83odAEAAACI5sBizZo11r9/f/f/kSNHjnt/9erVGZU2AAAAANEaWPTp08eKFi1qI0eOtMKFC2dOqgAAAABEd2Chu2vPmjXLypYtmzkpAgAAAJAzppvduHFj5qQGAAAAQPS2WHz00Uf+v2vVqmW9e/e2W265xcqUKWOxsbHJPnvjjTdmfCoBAAAAZP/AYvTo0cmeFyxY0GbOnHnc52JiYggsAAAAgBwoqMDiq6++yvyUAAAAAMgZg7f37NnjborndX9atWqV/fDDD1asWDFr3LixFShQILPSCQAAACC7D94+ePCgde3a1Ro0aGCbN292r02fPt3atm1r77zzjo0bN85atmxpO3fuzOz0AgAAAMiugcVLL71kv/32m02aNMnOP/98O3TokA0ePNiqVatmn3/+uc2ePdvq169vzz33XOanGAAAAED2DCwUPPTt29dq167tBmh/++23rhXj9ttvtzx58rjPtG7d2r0OAAAAIOcJKrDYvXu3nXvuuf7n33//vRtnoVYKzxlnnGHx8fEhrXzLli129913W82aNa1hw4Y2ceJE/3vbtm2zO++802rUqGHNmzcnaAEAAACye2BRqlQpV9AXn89n33zzjVWvXt2KFi3q/8ySJUvsrLPOCnrFSUlJ1rlzZzcYfMaMGTZgwAB75ZVX3F29tY5u3bq5YGXatGnWqlUr6969u+3YseNkthEAAABAVpgVSgV7jal44IEH3CxQv//+uz3yyCP+99esWWOjRo2yG264IaQZpipVqmT9+/e3QoUKWbly5eyyyy6zRYsWuYBCgcyUKVPcTFPly5e3BQsWuCCjR48eJ7elAAAAACIbWNx333124MABe+KJJ9wYi549e1qLFi3ce8OGDbM33njDdWXS54JVsmRJe+GFF9zfaqFYvHixLVy40Pr162fLli2zypUrJ5u+VuM7li5dGvoWAgAAAMgagUXu3LmtT58+7pHSjTfe6KaaVSBwsq6++mrXzalRo0bWpEkTe/bZZ13gEah48eJMZwsAAABEww3yUlOxYsWwEzF69GjXNUrdooYMGeIGgcfFxSX7jJ4nJCSkuZzExET3QPThdwWiD/kaiD7k65z9m4YdWGSEqlWruv+PHDlijz76qLVp0+a4GaYUVOTLly/N5axduzZT04nIWbFiBbsfiDLkayD6kK9ztogFFmqh0JiJa6+91v/aBRdcYEePHrUSJUrYxo0bj/t8yu5RKVWoUCHZuIxTbuqcyK07yin41BTHwClHvs405GtEDPk605Cvo49ujB1s5X3EAovt27e7KWQ1da2ms5WVK1dasWLF3EDt119/3Q4fPuxvpdBsUXo9LSp4UviMTvy2QPQhXwPRh3wdfUIpWwd1H4vMimgvvvhiN9PU+vXrXYAxYsQI69q1q9WtW9fdE0ODxdetW2fjx4+35cuXW9u2bSOVXAAAAABZMbBQ9DN27FjLnz+/tW/f3vr27Wu33367derUyf+e7vjdunVrmzlzpo0ZM8ZKly4dqeQCAAAAyKqDt9UF6uWXX071vbJly9qkSZNOeZoAAAAAZKMWCwAAAADRg8ACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAACEjcACAAAAQNgILAAAAABk78Bi165d1rNnT6tbt641aNDAhgwZYkeOHHHvbdu2ze68806rUaOGNW/e3L799ttIJhUAAABAVgwsfD6fCyri4+Pt3Xffteeff96+/vpre+GFF9x73bp1szPOOMOmTZtmrVq1su7du9uOHTsilVwAAAAAachtEbJx40ZbunSpfffddy6AEAUaw4YNsyuvvNK1WEyZMsUKFChg5cuXtwULFrggo0ePHpFKMgAAAICs1mJRokQJmzhxoj+o8Bw4cMCWLVtmlStXdkGFp3bt2i4QAQAAAJD1RKzFokiRIm5chScpKckmTZpkl156qe3evdtKliyZ7PPFixe3nTt3prnMxMRE90D04XcFog/5Gog+5Ouc/ZtGLLBIacSIEbZq1Sr78MMP7c0337S4uLhk7+t5QkJCmstYu3ZtJqcSkbJixQp2PhBlyNdA9CFf52y5s0pQ8dZbb7kB3BUqVLC8efPa3r17k31GQUW+fPnSXI6+G9h96pSbOidy645yVatWtdjY2EgnAzkR+TrTkK8RMeTrTEO+jj6HDh0KuvI+4oHFwIEDbfLkyS64aNKkiXutVKlStn79+mSf27Nnz3Hdo1JSwZPCZ3TitwWiD/kaiD7k6+gTStk6ovexePnll93MT6NGjbLrr7/e/3r16tXtl19+scOHD/tfW7RokXsdAAAAQNYTscBiw4YNNnbsWLv33nvdjE8asO09dMO8s846y/r06WPr1q2z8ePH2/Lly61t27aRSi4AAACArNgVau7cuW6U+SuvvOIegX799VcXdPTt29dat25tZcuWtTFjxljp0qUjlVwAAAAAWTGw6Ny5s3uciIIJTT8LAAAAIOuL6BgLAAAAANGBwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAABA2AgsAAAAAISNwAIAAAAAgQUAAACAyKPFAgAAAEDYCCwAAAAAhI3AAgAAAEDYCCwAAAAAhI3AAgAAAEB0BBYJCQnWokUL+/HHH/2vbdu2ze68806rUaOGNW/e3L799tuIphEAAABAFg4sjhw5Yg8//LCtW7fO/5rP57Nu3brZGWecYdOmTbNWrVpZ9+7dbceOHRFNKwAAAIDU5bYIWr9+vT3yyCMukAj0ww8/uBaLKVOmWIECBax8+fK2YMECF2T06NEjYukFAAAAkAVbLH766SerV6+evf/++8leX7ZsmVWuXNkFFZ7atWvb0qVLI5BKAAAAAFm6xaJjx46pvr57924rWbJksteKFy9uO3fuPEUpAwAAAJBtAosTiY+Pt7i4uGSv6bkGeaclMTHRPRB9+F2B6EO+BqIP+Tpn/6ZZMrDImzev7d27N9lrCiry5cuX5vfWrl2bySlDpKxYsYKdD0QZ8jUQfcjXOVuWDCxKlSrlBnYH2rNnz3Hdo1KqUKFCsnEZp9zUOZFbd5SrWrWqxcbGRjoZyInI15mGfI2IIV9nGvJ19Dl06FDQlfdZMrCoXr26jR8/3g4fPuxvpVi0aJEbwJ0WFTwpfEYnflsg+pCvgehDvo4+oZStI34fi9TUrVvXzjrrLOvTp4+7v4WCjOXLl1vbtm0jnTQAAAAA2SWwUGQ0duxYNztU69atbebMmTZmzBgrXbp0pJMGAAAAICt3hfr111+TPS9btqxNmjQpYukBAAAAkM1bLAAAAABkLwQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAMJGYAEAAAAgbAQWAAAAAKI7sDhy5Ig98cQTVqdOHatfv769/vrrkU4SAAAAgFTktixs+PDhtnLlSnvrrbdsx44d1qtXLytdurQ1bdo00kkDAAAAkB0Ci0OHDtnUqVNtwoQJdvHFF7vHunXr7N133yWwAAAAALKYLNsVas2aNXbs2DGrWbOm/7XatWvbsmXLLCkpKaJpAwAAAJBNAovdu3fb6aefbnFxcf7XzjjjDDfuYu/evRFNGwAAAIBs0hUqPj4+WVAh3vOEhIRkr3stGAcPHrTExESLlPNOy7K7M9s7cOCA5cqVZeNgRDHydeYhXyNSyNeZh3wdfQ4fPuz+D6bHUJYtCefNm/e4AMJ7ni9fvmSvqxVDtm7dapH03HVnRHT90Wz9+vWRTgJyKPJ15iFfI1LI15mHfB29VN4uVKhQ9gwsSpUqZX///bcbZ5E7d25/9ygFFUWKFEn22aJFi1q5cuVcMEKtNgAAAJAx1FKhoELl7fRk2cCiUqVKLqBYunSpu4+FLFq0yKpWrXpc8KDPFS9ePEIpBQAAAKJXei0VnizbaT1//vx24403Wv/+/W358uX25ZdfuhvkderUKdJJAwAAAJBCjM/n81kWHsCtwOLzzz93kdLdd99td955Z6STBQAAACC7tFh4rRbDhg2zJUuW2Pz58wkqgrR69WpbvHjxSe3zq6++2qZPn26R9NJLL9ntt99uWU1WTReiW1bJzwsWLLANGzZkyIwxH330kWVFFStWtB9//DHSyQCCorytPB4M1SHrBsMZYdu2bfbNN99YVqO8qzyMyMrSgQVOTrdu3Wzz5s0n9d0PP/zQmjdvzq4Hsoiskp/VWrxnz56wl/Pmm2/atGnTMiRNAIKzcOFCe+aZZzJkdz3xxBOuizqQrQZvIzKKFSvGrgeiRFbMz1m49y0Qtch3OFVosYgy6qrz22+/WZ8+fVwTqR79+vWz2rVr2/jx4929QIYMGWINGjSwiy++2L3//vvvp9p1Qst65ZVX3NiWatWqWZMmTVyXtGDcfPPNNnr06GSvdejQwcaOHev+njt3rhucr1m+NOvXww8/7G5weDKU3mbNmrk0tm7d2tXMyOTJk49rJta2Nm7c2P2tfTFo0CCrV6+eezz66KP+u7pv377dNamOGTPGLrnkklRreqZOnWpNmza1KlWquO8PGDDAf4PG3r17u2V37drVpUvberLdWZBzZZX87OUjTZ6hLoHy888/u/ymZbVs2dI+++wz/+d37Nhhd911l9WsWdMuu+wyGzhwoB09etSl5eWXX7affvop6C4LO3futAceeMDq1q3r8pnylbZb0x9quwNbP1R4uvLKK+3jjz9ON43Ko3rccMMNLo0pW4V27dplPXv2dPlfefymm25yMxMGnh9mzZrl0qBzmNKl6dGBUD300EPWq1evZK898sgj1rdvX3fM3XLLLVa9enWrUaOG3XvvvfbHH3+EtHwdr97EN4Hd/aZMmeLytvKpzg+//vprsq6PrVq1ctfoa665xn3WyzfKv8rHwXYNVnd2bYPSr/Xp2izqWqn0qGuVR/nwoosust9//z3dNOr1ESNGWP369d01NmXwlNa+07lI7z333HNu2Q0bNnTXdGQADd5G9Pj77799V155pe/NN9/0ffHFF74KFSr4evfu7du8ebPvt99+87300ku+xo0b+5YsWeLbunWr78UXX/RdfPHFvt27d7vvN2rUyDdt2jT392233earVq2ae75lyxZfz549fVdddZUvMTEx3XS88cYbvhYtWvif79y501exYkWXDi1L63z//fd927Zt882fP99Xr1493+uvv+4+O3r0aLfuYChtNWrU8M2YMcO3YcMG34gRI9xzre/PP//0Va5c2bdixQr/5++66y7fqFGj3N9DhgzxtW/f3rds2TLfmjVrfF26dPF16tTJvad0ad/p80rvpk2bkqXrxx9/dPvms88+c5+dPXu2r0qVKu659OrVy22j0rN+/XrfoEGDfLVr13ZpArJbftZxq3Xr+D5w4IDvjz/+8NWqVcv3zjvvuLR89NFHLt8tXLjQfb5r166+bt26ufcWLVrku+KKK3yTJk3yxcfH+4YOHerynZaRniNHjrjtu/fee10e/f77733XXHONb+DAge595SvlW8/ixYt9VatW9e3fvz/dNCqPXnTRRb65c+e6c4BoG3/44Qf//rr//vtd/l23bp1bj3dO884PSpuWt2DBAl+DBg385xYgFMrbl1xyiS8hIcF/3NesWdO9ruuG8rny988//+yOOe/4V15WHk/PsWPHXN7VMat8oeXruFe+/Oqrr9z17fnnn/fVrVvXt3fvXvd5/T127Fh3rH/88ccurygf7Nu3z+Vf5WOdn9Kj/KM8OXLkSHeNnj59uq969eq+zz//3L1/ww03+F577TX/51999VVfhw4d3N9ppVG07cp3OjesXr3a5V1toyid6e07nSt1jf/11199U6dOdc9VHkF4aLGIMqeddprFxsZa4cKF3UPuueceK1u2rJUuXdrVBAwePNhF72XKlHE16qpJPFEf7quuusrV+J177rl23333uVoE3agwPWpB0N03veVqZq/KlSu7dKim8cknn3StGuecc46rbbj88stt3bp1IW/vO++842oxVFtx/vnnu1aHChUq2KRJk1w3kEsvvdStW/755x9XU6M+55pxTJ9RK4NqM1VrMnz4cFcTE1gjcscdd7ht1w0YAxUoUMDtR7V+aBvUcqHtC9yGCy64wKWnfPnyrsZZN5b59NNPQ95G5FxZJT97Xap0DBcsWNANAlWeve2221xaVLPZvn17e+utt9zn1Mqi9CqNtWrVcq0rWrducKq8kydPHitRokS661WLiloOVCupPKqWhaefftrVeKqF8/rrr7fvvvvODQgXtUhoPZpFML00impjVeupc0Ag1Xxee+219tRTT7n8q7x86623HndH4ccee8y1Vug8o1aVDz74gC4nCJla2XRd9FoSvv32W5dXdHzef//9bpyV8rdaKnXNCfVaqXOId2Mz5bu4uDibOHGidenSxRo1auSubw8++KCdffbZNnPmTNu/f79rvT/jjDPc9U2tem+88Yb7rvK18q/ysc5P6VGe0LVRvRJ0jVbLn/Kk1i/Kw9412svD3riwtNLoUdp0btC5MNDhw4fT3XcxMTHuuq8yQ9u2bV1alF6EhzEW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",
      "text/plain": [
       "<Figure size 800x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(figsize=(8, 4))\n",
    "for idx, split_type in enumerate(overlap_df['split_type'].unique()):\n",
    "    subset = overlap_df[overlap_df['split_type'] == split_type]\n",
    "    ax.bar(\n",
    "        np.arange(len(subset)) + (idx - 0.5) * 0.35,\n",
    "        subset['overlap_count'],\n",
    "        width=0.35,\n",
    "        label=split_type.replace('_', ' '),\n",
    "    )\n",
    "ax.set_xticks(np.arange(len(subset)), subset['subset_pair'])\n",
    "ax.set_ylabel('Shared patients')\n",
    "ax.set_title('Patient leakage audit: initial image-level split vs corrected patient-level split')\n",
    "ax.legend()\n",
    "fig.tight_layout()\n",
    "fig.savefig(FIGURES / 'breakhis_leakage_audit.png', dpi=300)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0169d9fc",
   "metadata": {},
   "source": [
    "## What I found\n",
    "\n",
    "Once I grouped the files by patient code the overlap was obvious. The same patients were turning up on both sides of the image-level split I started with, which helps explain why that first accuracy looked stronger than it should have. After rebuilding the split at patient level the overlap drops to zero. I have kept the initial image-level version visible here because the contrast matters, but everything after this point uses the corrected patient-level split only.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "codex_research_commentary": true
   },
   "source": [
    "## How This Notebook Supports The Dissertation\n",
    "\n",
    "This is one of the key methodology notebooks. It demonstrates that the final image results are evaluated under a stricter and more defensible patient-level split, even if that makes performance lower than a leaked image-level split.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python (dissertation_dl)",
   "language": "python",
   "name": "dissertation_dl"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
