{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6d518a86",
   "metadata": {},
   "source": [
    "# Moduł 5: Data Quality by Design - walidacja i kwarantanna\n",
    "\n",
    "**Wersja:** uczestnika  \n",
    "**Cel:** wykonać wersjonowane reguły walidacyjne i rozdzielić dane na ścieżkę akceptowaną oraz kwarantannę z powodem  \n",
    "**Czas orientacyjny:** 40–50 min  \n",
    "**Dane:** customer360_raw_registrations.csv, data_contract.json  \n",
    "**Artefakt końcowy:** clean_customers + quarantine_customers + raport dla Data Stewarda\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": "1d8788c1",
   "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": "3d22d34e",
   "metadata": {},
   "source": [
    "## Granice interpretacji\n",
    "\n",
    "- Kontrakt danych jest tu uzgodnioną, wersjonowaną specyfikacją oczekiwań; nie zakładamy charakteru prawnego.\n",
    "- DLQ jest jednym z możliwych sposobów realizacji kwarantanny.\n",
    "- Pojedynczy błędny rekord i zmiana schematu całego strumienia mogą wymagać różnych reakcji.\n",
    "- Automatyczne wykrycie i przypisanie alertu nie oznacza automatycznej akceptacji ryzyka."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ed02769",
   "metadata": {},
   "source": [
    "## Reguła operacyjna: unikaj Shadow DQ\n",
    "Nie naprawiaj rekordów lokalnie tak, aby problem znikał tylko z notebooka. Walidator ma zachować surową wartość i kod naruszenia, a dane niespełniające reguły skierować do kontrolowanej reakcji. W rekomendacji wskaż najwcześniejszy skuteczny punkt Shift-Left: formularz, API, kontrakt albo walidator potoku."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "667c98f0",
   "metadata": {},
   "source": [
    "## Dane wejściowe i manifest\n",
    "Dane: `customer360_raw_registrations.csv`, `data_contract.json`.  \n",
    "Opis kolumn i reguł laboratoryjnych: `manifests/M05_data_manifest.csv` w paczce zbiorczej albo `data_manifest.csv` w paczce modułowej."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64e172f7",
   "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": "9137470f",
   "metadata": {},
   "source": [
    "## 1. Wczytanie danych i kontraktu\n",
    "\n",
    "Wczytaj dane jako `raw_source` i nie nadpisuj wartości źródłowych. Do walidacji użyj kopii `work`. Sprawdź wersję kontraktu i reguły. W tym case `age` ma zakres 18–110."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cea344a2",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "import json, re\n",
    "raw_source = pd.read_csv(DATA_DIR/'customer360_raw_registrations.csv')\n",
    "work = raw_source.copy(deep=True)\n",
    "contract = json.loads((DATA_DIR/'data_contract.json').read_text(encoding='utf-8'))\n",
    "print('contract:', contract['contract_name'], 'version', contract['version'])\n",
    "display(raw_source.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36f3a62c",
   "metadata": {},
   "source": [
    "## 2. Walidator rekordu\n",
    "\n",
    "Walidator ma zwracać listę kodów naruszeń. Nie usuwaj informacji o przyczynie odrzucenia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "413e9acc",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "def validate_record(row, rules):\n",
    "    # TODO: required, min/max, regex.\n",
    "    # Zwracaj listę kodów naruszeń, nie poprawiaj wartości w row.\n",
    "    return []\n",
    "\n",
    "work['violations']=work.apply(lambda r: validate_record(r, contract['rules']), axis=1)\n",
    "work['is_valid']=work['violations'].str.len().eq(0)\n",
    "display(work.loc[~work['is_valid'], ['registration_id','violations']].head(12))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33d58f99",
   "metadata": {},
   "source": [
    "## 3. Clean i kwarantanna\n",
    "\n",
    "Rozdziel rekordy, policz odsetek poprawnych i udział kwarantanny. Zlicz reguły generujące najwięcej naruszeń."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3eeaa1ef",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "clean_customers = pd.DataFrame()\n",
    "quarantine_customers = pd.DataFrame()\n",
    "violations = pd.DataFrame(columns=['rule','count'])\n",
    "valid_rate = np.nan\n",
    "quarantine_rate = np.nan\n",
    "# TODO: rozdziel work na clean/quarantine i policz metryki.\n",
    "display(violations)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "994cd083",
   "metadata": {},
   "source": [
    "## 4. Rekomendacja dla Data Stewarda i punkt Shift-Left\n",
    "\n",
    "Wskaż główną regułę, skalę, najwcześniejszy skuteczny punkt kontroli oraz reakcję. Rozróżnij pojedynczy błędny rekord od zmiany kontraktu całego strumienia. Notebook nie jest docelowym miejscem naprawy źródłowej."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83226196",
   "metadata": {
    "tags": [
     "exercise"
    ]
   },
   "outputs": [],
   "source": [
    "control_map=pd.DataFrame(columns=['problem','preferred_control_point','reaction','R','A'])\n",
    "recommendation={\n",
    " 'observation':'',\n",
    " 'hypothesis':'',\n",
    " 'short_term':'',\n",
    " 'target_state':'',\n",
    " 'R':'',\n",
    " 'A':'',\n",
    " 'recheck':''\n",
    "}\n",
    "# TODO: uzupełnij control_map i rekomendację.\n",
    "display(control_map)\n",
    "recommendation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6781a190",
   "metadata": {},
   "source": [
    "## Zadanie rozszerzające\n",
    "\n",
    "Dodaj do kontraktu nową regułę wraz z nazwą właściciela i uzasadnieniem progu."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e0bf5d3",
   "metadata": {},
   "source": [
    "## Kryteria akceptacji ćwiczenia\n",
    "- walidator zwraca kody naruszeń i zachowuje wartość źródłową\n",
    "- wersja robocza nie poprawia po cichu danych wejściowych\n",
    "- rekomendacja wskazuje miejsce Shift-Left, reakcję, R/A i miernik ponownej oceny\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": "a9ced370",
   "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": 5,
   "variant": "participant"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.x"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
