{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "57c85da6",
   "metadata": {},
   "source": [
    "# Moduł 8: Monitoring jakości danych po wdrożeniu\n",
    "\n",
    "**Wersja:** uczestnika  \n",
    "**Cel:** obliczyć PSI, nadać poziom sygnału i przełożyć go na runbook z właścicielem oraz kolejnym dowodem  \n",
    "**Czas orientacyjny:** 40–50 min  \n",
    "**Dane:** customer360_reference.csv, customer360_current.csv  \n",
    "**Artefakt końcowy:** tabela PSI + klasyfikacja sygnału + mini-runbook\n",
    "\n",
    "Notebook jest częścią wspólnego case'u **Customer 360**. Dane są syntetyczne i służą wyłącznie ćwiczeniu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22711928",
   "metadata": {},
   "source": [
    "## Jak pracować\n",
    "\n",
    "1. Uruchamiaj komórki od góry do dołu.\n",
    "2. Najpierw zapisz obserwację z danych. Dopiero później formułuj hipotezę o przyczynie.\n",
    "3. W rekomendacji wskaż dowód, właściciela działania i warunek ponownej oceny.\n",
    "4. Wynik statystyczny jest sygnałem diagnostycznym. Sam nie przesądza o zatrzymaniu modelu, zmianie progu ani retrainingu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee0091fb",
   "metadata": {},
   "source": [
    "## Granice interpretacji\n",
    "\n",
    "- PSI jest heurystyką porównania rozkładów. Progi 0,10 i 0,25 w ćwiczeniu są demonstracyjne i wymagają kalibracji operacyjnej.\n",
    "- Alert nie jest decyzją o retrainingu, rollbacku ani fallbacku.\n",
    "- Monitoring powinien łączyć warstwę danych, modelu i wyniku biznesowego oraz uwzględniać opóźnienie etykiet.\n",
    "- Każdy alert powinien mieć właściciela, instrukcję reakcji i kryterium zamknięcia."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "776eb0f1",
   "metadata": {},
   "source": [
    "## Reguła operacyjna laboratorium\n",
    "Poziom alertu porządkuje reakcję, ale nie zastępuje diagnozy. `Critical` w demonstracyjnej skali PSI nie oznacza automatycznie fallbacku, retrainingu ani zatrzymania automatyzacji. Fallback lub stop może być uzasadniony dopiero po potwierdzeniu ryzyka i zgodnie z zatwierdzoną polityką procesu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e31ca962",
   "metadata": {},
   "source": [
    "## Dane wejściowe i manifest\n",
    "Dane: `customer360_reference.csv`, `customer360_current.csv`.  \n",
    "Opis kolumn i reguł laboratoryjnych: `manifests/M08_data_manifest.csv` w paczce zbiorczej albo `data_manifest.csv` w paczce modułowej."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45921009",
   "metadata": {
    "tags": [
     "setup"
    ]
   },
   "outputs": [],
   "source": [
    "from pathlib import Path\n",
    "import sys\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "DATA_DIR = Path('data')\n",
    "if not DATA_DIR.exists():\n",
    "    DATA_DIR = Path('..') / 'data'\n",
    "assert DATA_DIR.exists(), 'Nie znaleziono katalogu data. Uruchom notebook z katalogu paczki lub zachowaj strukturę ZIP.'\n",
    "print('Python:', sys.version.split()[0])\n",
    "print('pandas:', pd.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e68dfa68",
   "metadata": {},
   "source": [
    "## 1. Funkcja PSI\n",
    "\n",
    "Użyj kwantyli reference jako granic koszyków. Zabezpiecz zera małym epsilonem."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0afe0823",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "ref=pd.read_csv(DATA_DIR/'customer360_reference.csv')\n",
    "cur=pd.read_csv(DATA_DIR/'customer360_current.csv')\n",
    "def psi(reference,current,bins=10):\n",
    "    # TODO\n",
    "    return np.nan"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ff344b21",
   "metadata": {},
   "source": [
    "## 2. PSI dla cech\n",
    "\n",
    "Policz PSI dla age i income_net. Nie interpretuj braków dochodu jako wartości liczbowej; raportuj ich zmianę osobno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fdb2105b",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "# TODO: psi_table i missing shift"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d52f97a0",
   "metadata": {},
   "source": [
    "## 3. Poziom sygnału – reguła demonstracyjna\n",
    "\n",
    "Na potrzeby ćwiczenia:\n",
    "- PSI < 0,10 → `Info`,\n",
    "- 0,10–<0,25 → `Warning`,\n",
    "- >=0,25 → `Critical`.\n",
    "\n",
    "Te granice są heurystyką roboczą, zależną od wolumenu, stabilności procesu i skutku biznesowego. Nie są normą ani automatem decyzyjnym."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0740e820",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "def alert_level(v):\n",
    "    # TODO: mapowanie Info / Warning / Critical.\n",
    "    return 'TODO'\n",
    "# TODO: zastosuj alert_level do psi_table."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "394d6df7",
   "metadata": {},
   "source": [
    "## 4. Mini-runbook i polityka fallback\n",
    "\n",
    "Dla najwyższego sygnału określ właściciela, kolejny dowód, czas/warunek reakcji i kryterium zamknięcia. Następnie uzupełnij politykę dla trzech poziomów.\n",
    "\n",
    "`Critical` nie oznacza automatycznego fallbacku. Fallback lub zatrzymanie automatyzacji rozważasz wtedy, gdy wpływ na decyzje jest potwierdzony albo ryzyko przekracza zatwierdzoną tolerancję, a bezpieczny tryb zastępczy jest zdefiniowany. Retraining pozostaje osobną decyzją."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "acfea1e0",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "runbook=pd.DataFrame(columns=['signal','owner','next_evidence','response_time','possible_decision','decision_gate','closure_condition'])\n",
    "policy=pd.DataFrame(columns=['level','default_response','guardrail'])\n",
    "# TODO: uzupełnij runbook i politykę Info/Warning/Critical.\n",
    "# Dodaj scenariusz what-if Critical, ale nie uruchamiaj fallbacku wyłącznie na podstawie PSI.\n",
    "display(runbook)\n",
    "display(policy)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "922c52ca",
   "metadata": {},
   "source": [
    "## Zadanie rozszerzające\n",
    "\n",
    "Dodaj do runbooka sygnał kompletności i sprawdź, czy właściciel oraz reakcja są takie same jak dla PSI."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22b3b675",
   "metadata": {},
   "source": [
    "## Kryteria akceptacji ćwiczenia\n",
    "- PSI liczysz względem koszyków wyznaczonych na reference; braki raportujesz osobno\n",
    "- progi 0,10/0,25 są jawnie opisane jako heurystyka demonstracyjna\n",
    "- critical nie uruchamia automatycznie retrainingu ani fallbacku; decyzja wymaga potwierdzenia wpływu i polityki procesu\n",
    "\n",
    "Przed oddaniem wykonaj **Restart Kernel and Run All**. Notebook nie powinien zależeć od ręcznie ustawionego stanu poprzednich komórek."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4210547b",
   "metadata": {},
   "source": [
    "## Co oddajesz\n",
    "\n",
    "Zachowaj tabelę lub wykres będący wynikiem ćwiczenia oraz krótki komentarz: **obserwacja → hipoteza → potrzebny dowód → rekomendacja / dalszy krok**. Jeżeli nie da się rozstrzygnąć przyczyny na podstawie danych z notebooka, oznacz ją jako hipotezę i wskaż brakujący artefakt."
   ]
  }
 ],
 "metadata": {
  "course": {
   "generated": "2026-08-28",
   "module": 8,
   "variant": "participant"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.x"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
