{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cell-000",
   "metadata": {},
   "source": [
    "# Lesson 2 — Temporal Computations & Exposure Calculations\n",
    "\n",
    "**Companion notebook** for the Lesson 2 slides. Data: `../data/motor/`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-001",
   "metadata": {},
   "outputs": [],
   "source": [
    "def check(label, got, expected, tol=1e-6):\n",
    "    \"\"\"Compare your answer with the expected value; prints ✅ or ❌ (never raises).\"\"\"\n",
    "    import math\n",
    "    try:\n",
    "        if isinstance(expected, float) or isinstance(got, float):\n",
    "            ok = math.isclose(float(got), float(expected), rel_tol=tol, abs_tol=tol)\n",
    "        elif isinstance(expected, (list, tuple)) and expected and isinstance(expected[0], str):\n",
    "            ok = list(got) == list(expected)\n",
    "        elif hasattr(expected, \"__len__\") and not isinstance(expected, str):\n",
    "            import numpy as np\n",
    "            ok = np.allclose(np.asarray(got, dtype=float), np.asarray(expected, dtype=float), rtol=tol, atol=tol)\n",
    "        else:\n",
    "            ok = got == expected\n",
    "    except Exception:\n",
    "        ok = False\n",
    "    print((\"✅ \" if ok else \"❌ \") + f\"{label}: got {got!r}, expected {expected!r}\")\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "EVAL = pd.Timestamp(\"2025-12-31\")\n",
    "policies = pd.read_csv(\"../data/motor/policies.csv\",\n",
    "                       parse_dates=[\"inception_date\", \"expiry_date\", \"cancellation_date\"])\n",
    "policies.dtypes.head(6)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-002",
   "metadata": {},
   "source": [
    "## 1. Parsing dates — be explicit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-003",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(pd.to_datetime(\"03/04/2024\"), \"(US)  vs \", pd.to_datetime(\"03/04/2024\", dayfirst=True), \"(UK)\")\n",
    "pd.to_datetime([\"2024-02-30\", \"2024-03-01\"], format=\"%Y-%m-%d\", errors=\"coerce\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-004",
   "metadata": {},
   "source": [
    "## 2. `.dt` properties, `Timedelta` and `DateOffset`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-005",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = pd.to_datetime(pd.Series([\"2024-02-29\", \"2023-12-31\"]))\n",
    "print(d.dt.year.tolist(), d.dt.day_name().tolist(), d.dt.is_leap_year.tolist())\n",
    "start = pd.Timestamp(\"2024-01-15\")\n",
    "print(start + pd.Timedelta(days=365), \"vs\", start + pd.DateOffset(years=1))\n",
    "print(pd.Timestamp(\"2024-02-29\") + pd.DateOffset(years=1))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-006",
   "metadata": {},
   "source": [
    "## 3. Policy status and cover dates"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-007",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = policies.copy()\n",
    "p[\"cover_end\"] = p[[\"expiry_date\", \"cancellation_date\"]].min(axis=1)\n",
    "p[\"earned_end\"] = p[\"cover_end\"].clip(upper=EVAL)\n",
    "p[\"status\"] = np.select([p[\"cancellation_date\"].notna(), p[\"expiry_date\"] > EVAL],\n",
    "                        [\"Canceled\", \"In force\"], \"Expired\")\n",
    "p[\"status\"].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-008",
   "metadata": {},
   "source": [
    "---\n",
    "## Your turn 1 — exact calendar-year exposure\n",
    "\n",
    "Each covered day counts 1/365, or 1/366 in a leap year. Cover runs from `start` to\n",
    "`end` **inclusive**. Return a Series of years of exposure earned in `year`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-009",
   "metadata": {},
   "outputs": [],
   "source": [
    "def calendar_exposure(start: pd.Series, end: pd.Series, year: int) -> pd.Series:\n",
    "    ...  # your code here\n",
    "\n",
    "\n",
    "cy = None\n",
    "try:\n",
    "    cy = pd.DataFrame({y: calendar_exposure(p[\"inception_date\"], p[\"earned_end\"], y) for y in range(2022, 2026)})\n",
    "    p[\"exposure\"] = cy.sum(axis=1)\n",
    "except Exception:          # the function isn't finished yet\n",
    "    cy = None\n",
    "\n",
    "check(\"total exposure\", None if cy is None else round(cy.sum().sum(), 2), 18579.58)\n",
    "check(\"calendar year 2024\", None if cy is None else round(cy[2024].sum(), 2), 5410.65)\n",
    "check(\"leap-year renewal POL000347\",\n",
    "      None if cy is None else round(cy.loc[p[\"policy_id\"] == \"POL000347\"].sum(axis=1).iloc[0], 6), 1.00134)\n",
    "check(\"canceled POL000016\",\n",
    "      None if cy is None else round(cy.loc[p[\"policy_id\"] == \"POL000016\"].sum(axis=1).iloc[0], 4), 0.7381)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-010",
   "metadata": {},
   "source": [
    "## Your turn 2 — exposure by status and by policy year"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-011",
   "metadata": {},
   "outputs": [],
   "source": [
    "by_status = by_policy_year = None\n",
    "...  # your code here\n",
    "check(\"in-force exposure\", None if by_status is None else by_status[\"In force\"], 2741.50)\n",
    "check(\"policy year 2023\", None if by_policy_year is None else by_policy_year[2023], 5251.15)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-012",
   "metadata": {},
   "source": [
    "## Your turn 3 — earned premium by calendar year\n",
    "\n",
    "Earned premium for a year = written premium × (days covered in that year ÷ days in\n",
    "the term). Hint: `cy` × days-in-year gives covered days."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-013",
   "metadata": {},
   "outputs": [],
   "source": [
    "earned_premium = None\n",
    "if cy is not None:\n",
    "    term_days = (p[\"expiry_date\"] - p[\"inception_date\"]).dt.days + 1\n",
    "    year_days = pd.Series({y: 366 if pd.Timestamp(f\"{y}-01-01\").is_leap_year else 365 for y in cy.columns})\n",
    "    ...  # your code here\n",
    "check(\"earned premium 2025\", None if earned_premium is None else round(earned_premium[2025]), 9984868)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cell-014",
   "metadata": {},
   "source": [
    "## Frequency by accident year"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cell-015",
   "metadata": {},
   "outputs": [],
   "source": [
    "claims = pd.read_csv(\"../data/motor/claims.csv\", parse_dates=[\"accident_date\", \"report_date\"])\n",
    "if cy is not None:\n",
    "    (claims.groupby(claims[\"accident_date\"].dt.year).size() / cy.sum()).round(4)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
