diff --git a/docs/_toc.yml b/docs/_toc.yml index 829540d32..d3d3152be 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -43,6 +43,7 @@ parts: - file: friedland/chapter_9.ipynb - file: friedland/chapter_10.ipynb - file: friedland/chapter_11.ipynb + - file: friedland/chapter_12.ipynb - file: friedland/chapter_15_monitoring.ipynb - chapters: - file: gallery/index.md diff --git a/docs/friedland/chapter_12.ipynb b/docs/friedland/chapter_12.ipynb new file mode 100644 index 000000000..67963bb54 --- /dev/null +++ b/docs/friedland/chapter_12.ipynb @@ -0,0 +1,5196 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cell_md_1", + "metadata": {}, + "source": [ + "# Chapter 12 - Case Outstanding Development Technique\n", + "\n", + "> \"The case outstanding development technique is used to estimate future development on known claims (i.e., case outstanding development). The method can also be used to estimate total unpaid claims, including both development on known claims and pure IBNR... In this chapter, we refer to this technique as Approach #1. We also present an example for a self-insurer of the standard development technique applied to case outstanding in which the case outstanding development factors to ultimate are derived from industry-based benchmark reported and paid claim development factors; we refer to this example as Approach #2.\"\n", + "> -- Friedland, Chapter 12\n", + "\n", + "This notebook reconciles the exhibits from Friedland Chapter 12:\n", + "- **Exhibit I**: U.S. Industry Auto (Approach #1) - Sheets 1 through 5\n", + "- **Exhibit II**: XYZ Insurer - Auto BI (Approach #1) - Sheets 1 through 7\n", + "- **Exhibit III**: Self-Insurer Case Outstanding Only - General Liability (Approach #2)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cell_code_2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:32.825162Z", + "iopub.status.busy": "2026-09-28T14:20:32.824487Z", + "iopub.status.idle": "2026-09-28T14:20:35.870108Z", + "shell.execute_reply": "2026-09-28T14:20:35.869116Z" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import chainladder as cl\n", + "import warnings\n", + "from IPython.display import display as nb_display\n", + "\n", + "# Suppress minor warnings for clean presentation\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "\n", + "def format_exh(\n", + " exh_df: pd.DataFrame,\n", + " value_cols: list[str] = [],\n", + " factor_cols: list[str] = [],\n", + " other_formats: dict = {},\n", + " origin_format: str = \"{:%Y}\",\n", + " origin_name: str = \"Accident Year\",\n", + " total_cols: list[str] = [],\n", + "):\n", + " \"\"\"\n", + " Actuarial exhibit styler matching Chapter 11.\n", + " Adds column numbering (1), (2), ..., currency/factor formatting, and summary totals.\n", + " \"\"\"\n", + "\n", + " def _format(\n", + " df: pd.DataFrame,\n", + " val_c: list[str] = [],\n", + " fac_c: list[str] = [],\n", + " oth_f: dict = {},\n", + " orig_f: str = None,\n", + " orig_n: str = origin_name,\n", + " ):\n", + " idx_name = df.index.name if df.index.name else \"index\"\n", + " format_dict = {}\n", + " if orig_f:\n", + " format_dict[orig_n] = orig_f\n", + " format_dict.update({x: \"{:,.0f}\" for x in val_c})\n", + " format_dict.update({x: \"{:,.3f}\" for x in fac_c})\n", + " format_dict.update(oth_f)\n", + "\n", + " return (\n", + " df\n", + " .reset_index()\n", + " .rename(columns={idx_name: orig_n})\n", + " .style.hide(axis=\"index\")\n", + " .set_properties(**{\"text-align\": \"right\"})\n", + " .format(format_dict)\n", + " )\n", + "\n", + " col_idx = pd.DataFrame(\n", + " [[f\"({i + 2})\" for i in range(len(exh_df.columns))]],\n", + " columns=exh_df.columns,\n", + " index=[\"(1)\"],\n", + " )\n", + " table_styler = _format(col_idx, orig_f=None, orig_n=origin_name).concat(\n", + " _format(\n", + " exh_df,\n", + " val_c=value_cols,\n", + " fac_c=factor_cols,\n", + " oth_f=other_formats,\n", + " orig_f=origin_format,\n", + " orig_n=origin_name,\n", + " )\n", + " )\n", + "\n", + " if len(total_cols) > 0:\n", + " table_styler = table_styler.concat(\n", + " _format(\n", + " exh_df.agg([\"sum\"]).rename(index={\"sum\": \"Total\"}),\n", + " val_c=value_cols,\n", + " fac_c=factor_cols,\n", + " oth_f={x: \"\" for x in exh_df.columns if x not in total_cols},\n", + " orig_f=None,\n", + " orig_n=origin_name,\n", + " )\n", + " )\n", + " return table_styler" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_3", + "metadata": {}, + "source": [ + "## Exhibit I Analysis\n", + "\n", + "In Exhibit I, Friedland demonstrates **Approach #1** using the U.S. Industry Auto dataset (`usauto`). This technique estimates unpaid claims by analyzing the historical relationships of incremental paid claims and case reserve runoff to prior carried case reserves.\n", + "\n", + "Following the design pattern established in **Chapter 11**, we fit our dedicated `cl.CaseOutstanding` models upfront to power the subsequent exhibits.\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_4", + "metadata": {}, + "source": [ + "## Exhibit I Sheet 1\n", + "\n", + "### Case Outstanding and Incremental Paid Claims ($000)\n", + "\n", + "- **Case Outstanding**: Calculated dynamically as $\\text{Case Outstanding} = \\text{Reported Claims} - \\text{Paid Claims}$.\n", + "- **Incremental Paid Claims**: Derived dynamically from Cumulative Paid Claims via `.cum_to_incr()`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cell_code_5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:35.873747Z", + "iopub.status.busy": "2026-09-28T14:20:35.873014Z", + "iopub.status.idle": "2026-09-28T14:20:36.518620Z", + "shell.execute_reply": "2026-09-28T14:20:36.517472Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PART 1 - Case Outstanding Triangle as of (months) ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120
199818,478,2339,937,9705,506,9112,892,5191,440,783767,842413,097242,778169,22298,117
199918,544,2919,955,0345,623,5223,060,4311,520,760764,736443,528284,732185,233
200019,034,93310,395,4645,969,1943,217,9371,567,806842,849457,854304,704
200119,401,81010,487,9145,936,4613,056,2021,532,147777,926421,141
200220,662,46111,176,3306,198,5093,350,9671,609,188785,497
200321,078,65111,098,1196,398,2193,431,2101,634,690
200421,047,53911,150,4596,316,9953,201,985
200521,260,17211,087,8326,141,416
200620,973,90811,034,842
200721,623,594
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "PART 2 - Incremental Paid Claims Triangle as of (months) ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120
199818,539,25414,691,7856,830,9693,830,0312,004,496868,887455,900225,555108,57988,731
199920,410,19315,680,4917,168,7183,899,8392,049,291953,511463,714253,051121,726
200022,120,84316,855,1717,413,2684,173,1032,172,8951,004,821544,233248,891
200122,992,25917,103,9397,671,6374,326,0812,269,5201,015,365499,620
200224,092,78217,702,5318,108,4904,449,0812,401,4921,052,839
200324,084,45117,315,1617,670,7204,513,8692,346,453
200424,369,77017,120,0937,746,8154,537,994
200525,100,69717,601,5327,942,765
200625,608,77617,997,721
200727,229,969
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "usauto = cl.load_sample(\"usauto\")\n", + "paid_cumulative = usauto[\"paid\"]\n", + "incurred = usauto[\"incurred\"]\n", + "\n", + "# 1. Triangle of Case Outstanding ($000)\n", + "case_outstanding = incurred - paid_cumulative\n", + "\n", + "# 2. Triangle of Incremental Paid Claims ($000)\n", + "incremental_paid = paid_cumulative.cum_to_incr()\n", + "\n", + "# - m_all: fits with all historical origin periods for review\n", + "# - m_5: fits 5-year averages\n", + "# - m_3: fits 3-year averages (used for selected ratios)\n", + "m_all = cl.CaseOutstanding(\n", + " paid_to_incurred=(\"paid\", \"incurred\"), paid_n_periods=-1, case_n_periods=-1\n", + ").fit(usauto)\n", + "m_5 = cl.CaseOutstanding(\n", + " paid_to_incurred=(\"paid\", \"incurred\"), paid_n_periods=5, case_n_periods=5\n", + ").fit(usauto)\n", + "m_3 = cl.CaseOutstanding(\n", + " paid_to_incurred=(\"paid\", \"incurred\"), paid_n_periods=3, case_n_periods=3\n", + ").fit(usauto)\n", + "\n", + "print(\"PART 1 - Case Outstanding Triangle as of (months) ($000):\")\n", + "nb_display(case_outstanding.style.format(\"{:,.0f}\", na_rep=\"\"))\n", + "\n", + "print(\"\\nPART 2 - Incremental Paid Claims Triangle as of (months) ($000):\")\n", + "nb_display(incremental_paid.style.format(\"{:,.0f}\", na_rep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_6", + "metadata": {}, + "source": [ + "## Exhibit I Sheet 2\n", + "\n", + "### Ratio of Incremental Paid Claims to Previous Case Outstanding\n", + "\n", + "This sheet presents:\n", + "- **PART 1 - Ratio Triangle**: Evaluated natively via `cl.CaseOutstanding.paid_to_prior_case_`, calculating the proportion of previous case reserves paid out during each subsequent development lag:\n", + " $$\\text{Ratio}_{d} = \\frac{\\text{Incremental Paid}_{d}}{\\text{Case Outstanding}_{d-12}}$$\n", + "- **PART 2 - Historical Averages**: Summary table combining the Latest 5 Years (`m_5.paid_ldf_`), Latest 3 Years (`m_3.paid_ldf_`), and 5-Year Medial (5x1).\n", + "- **PART 3 - Selected Ratios**: Actuarial selections based on the Latest 3 Years average with **To Ult = 1.100**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "cell_code_7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.521963Z", + "iopub.status.busy": "2026-09-28T14:20:36.521335Z", + "iopub.status.idle": "2026-09-28T14:20:36.576513Z", + "shell.execute_reply": "2026-09-28T14:20:36.575755Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PART 1 - Ratio of Incremental Paid Claims to Previous Case Outstanding (cl.Triangle):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
24-3636-4848-6060-7272-8484-9696-108108-120120-132
19980.79500.68700.69500.69300.60300.59400.54600.44700.5240
19990.84600.72000.69300.67000.62700.60600.57100.4280
20000.88500.71300.69900.67500.64100.64600.5440
20010.88200.73100.72900.74300.66300.6420
20020.85700.72600.71800.71700.6540
20030.82100.69100.70500.6840
20040.81300.69500.7180
20050.82800.7160
20060.8580
2007
" + ], + "text/plain": [ + " 24-36 36-48 48-60 60-72 72-84 84-96 96-108 108-120 120-132\n", + "1998 0.795 0.687 0.695 0.693 0.603 0.594 0.546 0.447 0.524\n", + "1999 0.846 0.720 0.693 0.670 0.627 0.606 0.571 0.428 NaN\n", + "2000 0.885 0.713 0.699 0.675 0.641 0.646 0.544 NaN NaN\n", + "2001 0.882 0.731 0.729 0.743 0.663 0.642 NaN NaN NaN\n", + "2002 0.857 0.726 0.718 0.717 0.654 NaN NaN NaN NaN\n", + "2003 0.821 0.691 0.705 0.684 NaN NaN NaN NaN NaN\n", + "2004 0.813 0.695 0.718 NaN NaN NaN NaN NaN NaN\n", + "2005 0.828 0.716 NaN NaN NaN NaN NaN NaN NaN\n", + "2006 0.858 NaN NaN NaN NaN NaN NaN NaN NaN\n", + "2007 NaN NaN NaN NaN NaN NaN NaN NaN NaN" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "PART 2 - Historical Averages of the Ratio:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24-3636-4848-6060-7272-8484-9696-108108-120120-132To Ult
Latest 5 Years0.8360.7120.7140.6980.6380.6220.5530.4370.524
Latest 3 Years0.8330.7010.7140.7140.6530.6310.5530.4370.524
5-Year Medial (5x1)0.8350.7120.7140.6920.6410.6240.5460.4370.524
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "PART 3 - Selected Ratio of Incremental Paid Claims to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120To Ult
Selected0.8330.7010.7140.7140.6530.6310.5530.4370.5241.100
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(\n", + " \"PART 1 - Ratio of Incremental Paid Claims to Previous Case Outstanding (cl.Triangle):\"\n", + ")\n", + "tri_paid = m_all.paid_to_prior_case_\n", + "nb_display(tri_paid.round(3))\n", + "\n", + "print(\"\\nPART 2 - Historical Averages of the Ratio:\")\n", + "df_paid = tri_paid.to_frame(origin_as_datetime=False)\n", + "\n", + "\n", + "def calc_medial_5x1(s):\n", + " vals = s.dropna().tail(5).values\n", + " if len(vals) > 2:\n", + " return np.sort(vals)[1:-1].mean()\n", + " elif len(vals) > 0:\n", + " return vals.mean()\n", + " return np.nan\n", + "\n", + "\n", + "medial_5x1_paid = df_paid.apply(calc_medial_5x1)\n", + "\n", + "averages_paid_df = pd.DataFrame({\n", + " \"Latest 5 Years\": m_5.paid_ldf_.to_frame().iloc[0],\n", + " \"Latest 3 Years\": m_3.paid_ldf_.to_frame().iloc[0],\n", + " \"5-Year Medial (5x1)\": medial_5x1_paid,\n", + "}).T\n", + "averages_paid_df[\"To Ult\"] = np.nan\n", + "nb_display(averages_paid_df.style.format(\"{:.3f}\", na_rep=\"\"))\n", + "\n", + "print(\n", + " \"\\nPART 3 - Selected Ratio of Incremental Paid Claims to Previous Case Outstanding:\"\n", + ")\n", + "dev_cols_e1 = [24, 36, 48, 60, 72, 84, 96, 108, 120]\n", + "selected_paid_ratios = pd.Series(m_3.paid_ldf_.values[0, 0, 0, :], index=dev_cols_e1)\n", + "selected_paid_ratios[\"To Ult\"] = 1.100\n", + "selected_paid_df = pd.DataFrame([selected_paid_ratios], index=[\"Selected\"])\n", + "nb_display(selected_paid_df.style.format(\"{:.3f}\", na_rep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_8", + "metadata": {}, + "source": [ + "## Exhibit I Sheet 3\n", + "\n", + "### Ratio of Case Outstanding to Previous Case Outstanding\n", + "\n", + "This sheet presents:\n", + "- **PART 1 - Ratio Triangle**: Evaluated natively via `cl.CaseOutstanding.case_to_prior_case_`, calculating the proportion of case reserves remaining after each subsequent development lag:\n", + " $$\\text{Ratio}_{d} = \\frac{\\text{Case Outstanding}_{d}}{\\text{Case Outstanding}_{d-12}}$$\n", + "- **PART 2 - Historical Averages**: Summary table combining the Latest 5 Years (`m_5.case_ldf_`), Latest 3 Years (`m_3.case_ldf_`), and 5-Year Medial (5x1).\n", + "- **PART 3 - Selected Ratios**: Actuarial selections based on the Latest 3 Years average with **To Ult = 0.000**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cell_code_9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.579502Z", + "iopub.status.busy": "2026-09-28T14:20:36.578999Z", + "iopub.status.idle": "2026-09-28T14:20:36.627293Z", + "shell.execute_reply": "2026-09-28T14:20:36.626550Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PART 1 - Ratio of Case Outstanding to Previous Case Outstanding (cl.Triangle):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
24-3636-4848-6060-7272-8484-9696-108108-120120-132
19980.53800.55400.52500.49800.53300.53800.58800.69700.5800
19990.53700.56500.54400.49700.50300.58000.64200.6510
20000.54600.57400.53900.48700.53800.54300.6660
20010.54100.56600.51500.50100.50800.5410
20020.54100.55500.54100.48000.4880
20030.52700.57700.53600.4760
20040.53000.56700.5070
20050.52200.5540
20060.5260
2007
" + ], + "text/plain": [ + " 24-36 36-48 48-60 60-72 72-84 84-96 96-108 108-120 120-132\n", + "1998 0.538 0.554 0.525 0.498 0.533 0.538 0.588 0.697 0.58\n", + "1999 0.537 0.565 0.544 0.497 0.503 0.580 0.642 0.651 NaN\n", + "2000 0.546 0.574 0.539 0.487 0.538 0.543 0.666 NaN NaN\n", + "2001 0.541 0.566 0.515 0.501 0.508 0.541 NaN NaN NaN\n", + "2002 0.541 0.555 0.541 0.480 0.488 NaN NaN NaN NaN\n", + "2003 0.527 0.577 0.536 0.476 NaN NaN NaN NaN NaN\n", + "2004 0.530 0.567 0.507 NaN NaN NaN NaN NaN NaN\n", + "2005 0.522 0.554 NaN NaN NaN NaN NaN NaN NaN\n", + "2006 0.526 NaN NaN NaN NaN NaN NaN NaN NaN\n", + "2007 NaN NaN NaN NaN NaN NaN NaN NaN NaN" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "PART 2 - Historical Averages of the Ratio:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24-3636-4848-6060-7272-8484-9696-108108-120120-132To Ult
Latest 5 Years0.5290.5640.5280.4880.5140.5510.6320.6740.580
Latest 3 Years0.5260.5660.5280.4860.5110.5550.6320.6740.580
5-Year Medial (5x1)0.5270.5620.5300.4880.5150.5420.6420.6740.580
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "PART 3 - Selected Ratio of Case Outstanding to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120To Ult
Selected0.5260.5660.5280.4860.5110.5550.6320.6740.5800.000
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "print(\"PART 1 - Ratio of Case Outstanding to Previous Case Outstanding (cl.Triangle):\")\n", + "tri_case = m_all.case_to_prior_case_\n", + "nb_display(tri_case.round(3))\n", + "\n", + "print(\"\\nPART 2 - Historical Averages of the Ratio:\")\n", + "df_case = tri_case.to_frame(origin_as_datetime=False)\n", + "medial_5x1_case = df_case.apply(calc_medial_5x1)\n", + "\n", + "averages_case_df = pd.DataFrame({\n", + " \"Latest 5 Years\": m_5.case_ldf_.to_frame().iloc[0],\n", + " \"Latest 3 Years\": m_3.case_ldf_.to_frame().iloc[0],\n", + " \"5-Year Medial (5x1)\": medial_5x1_case,\n", + "}).T\n", + "averages_case_df[\"To Ult\"] = np.nan\n", + "nb_display(averages_case_df.style.format(\"{:.3f}\", na_rep=\"\"))\n", + "\n", + "print(\"\\nPART 3 - Selected Ratio of Case Outstanding to Previous Case Outstanding:\")\n", + "selected_case_ratios = pd.Series(m_3.case_ldf_.values[0, 0, 0, :], index=dev_cols_e1)\n", + "selected_case_ratios[\"To Ult\"] = 0.000\n", + "selected_case_df = pd.DataFrame([selected_case_ratios], index=[\"Selected\"])\n", + "nb_display(selected_case_df.style.format(\"{:.3f}\", na_rep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_10", + "metadata": {}, + "source": [ + "## Exhibit I Sheet 4\n", + "\n", + "### Projection of Paid Claims ($000)\n", + "\n", + "Using the selected ratios:\n", + "1. **Complete the Square of Case Outstanding**:\n", + " $$\\text{Case}_{d} = \\text{Case}_{d-12} \\times \\text{Selected Case Ratio}_{d}$$\n", + "2. **Complete the Square of Incremental Paid Claims**:\n", + " $$\\text{Incr Paid}_{d} = \\text{Case}_{d-12} \\times \\text{Selected Paid Ratio}_{d}$$\n", + " $$\\text{Incr Paid}_{\\text{To Ult}} = \\text{Case}_{120} \\times \\text{Selected Paid Ratio}_{\\text{To Ult}}$$\n", + "3. **Cumulative Paid Claims**: Cumulative sum across development lags through `To Ult`.\n", + "\n", + "Projected cells beyond the latest diagonal are highlighted in light blue (`#d1ecf1`).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "cell_code_11", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.630186Z", + "iopub.status.busy": "2026-09-28T14:20:36.629767Z", + "iopub.status.idle": "2026-09-28T14:20:36.738951Z", + "shell.execute_reply": "2026-09-28T14:20:36.738148Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1. Case Outstanding as of (months) ($000) [Projected cells highlighted]:" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120To Ult
199818,478,2339,937,9705,506,9112,892,5191,440,783767,842413,097242,778169,22298,1170
199918,544,2919,955,0345,623,5223,060,4311,520,760764,736443,528284,732185,233107,4000
200019,034,93310,395,4645,969,1943,217,9371,567,806842,849457,854304,704205,306119,0390
200119,401,81010,487,9145,936,4613,056,2021,532,147777,926421,141266,046179,258103,9360
200220,662,46111,176,3306,198,5093,350,9671,609,188785,497435,836275,329185,513107,5630
200321,078,65111,098,1196,398,2193,431,2101,634,690835,581463,625292,884197,342114,4210
200421,047,53911,150,4596,316,9953,201,9851,556,120795,419441,341278,807187,857108,9220
200521,260,17211,087,8326,141,4163,242,1941,575,661805,407446,883282,308190,216110,2890
200620,973,90811,034,8426,241,7673,295,1711,601,407818,568454,185286,921193,324112,0910
200721,623,59411,369,8886,431,2823,395,2211,650,030843,422467,976295,632199,193115,4950
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "2. Incremental Paid Claims as of (months) ($000) [Projected cells highlighted]:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120To Ult
199818,539,25414,691,7856,830,9693,830,0312,004,496868,887455,900225,555108,57988,731107,929
199920,410,19315,680,4917,168,7183,899,8392,049,291953,511463,714253,051121,72697,126118,140
200022,120,84316,855,1717,413,2684,173,1032,172,8951,004,821544,233248,891133,269107,651130,943
200122,992,25917,103,9397,671,6374,326,0812,269,5201,015,365499,620233,053116,36193,994114,330
200224,092,78217,702,5318,108,4904,449,0812,401,4921,052,839495,995241,185120,42197,273118,319
200324,084,45117,315,1617,670,7204,513,8692,346,4531,066,844527,620256,563128,100103,475125,863
200424,369,77017,120,0937,746,8154,537,9942,287,3991,015,567502,260244,231121,94398,502119,814
200525,100,69717,601,5327,942,7654,384,2202,316,1231,028,320508,568247,298123,47499,739121,318
200625,608,77617,997,7217,732,7604,455,8582,353,9681,045,123516,877251,339125,491101,369123,301
200727,229,96918,015,4297,967,5464,591,1492,425,4411,076,855532,571258,971129,302104,446127,044
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "3. Cumulative Paid Claims as of (months) ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120To Ult
199818,539,25433,231,03940,062,00843,892,03945,896,53546,765,42247,221,32247,446,87747,555,45647,644,18747,752,116
199920,410,19336,090,68443,259,40247,159,24149,208,53250,162,04350,625,75750,878,80851,000,53451,097,66051,215,801
200022,120,84338,976,01446,389,28250,562,38552,735,28053,740,10154,284,33454,533,22554,666,49454,774,14654,905,089
200122,992,25940,096,19847,767,83552,093,91654,363,43655,378,80155,878,42156,111,47456,227,83556,321,82956,436,159
200224,092,78241,795,31349,903,80354,352,88456,754,37657,807,21558,303,21058,544,39558,664,81758,762,09058,880,409
200324,084,45141,399,61249,070,33253,584,20155,930,65456,997,49857,525,11857,781,68157,909,78158,013,25658,139,120
200424,369,77041,489,86349,236,67853,774,67256,062,07157,077,63857,579,89857,824,13057,946,07258,044,57458,164,388
200525,100,69742,702,22950,644,99455,029,21457,345,33758,373,65758,882,22559,129,52359,252,99759,352,73659,474,054
200625,608,77643,606,49751,339,25755,795,11558,149,08459,194,20659,711,08459,962,42360,087,91560,189,28360,312,584
200727,229,96945,245,39853,212,94457,804,09360,229,53461,306,38961,838,96162,097,93162,227,23362,331,67962,458,723
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "case_df = case_outstanding.to_frame(origin_as_datetime=False)\n", + "case_df.columns = [int(col) for col in case_df.columns]\n", + "case_df.index = [int(getattr(idx, \"year\", idx)) for idx in case_df.index]\n", + "\n", + "incr_paid_df = incremental_paid.to_frame(origin_as_datetime=False)\n", + "incr_paid_df.columns = [int(col) for col in incr_paid_df.columns]\n", + "incr_paid_df.index = [int(getattr(idx, \"year\", idx)) for idx in incr_paid_df.index]\n", + "\n", + "# 1. Complete the Square of Case Outstanding\n", + "completed_case_df = case_df.copy()\n", + "completed_case_df[\"To Ult\"] = 0.0\n", + "is_projected_case = pd.DataFrame(\n", + " False, index=completed_case_df.index, columns=completed_case_df.columns\n", + ")\n", + "\n", + "for ay in completed_case_df.index:\n", + " for d in dev_cols_e1:\n", + " if pd.isna(completed_case_df.loc[ay, d]):\n", + " prev_case = completed_case_df.loc[ay, d - 12]\n", + " completed_case_df.loc[ay, d] = prev_case * selected_case_ratios[d]\n", + " is_projected_case.loc[ay, d] = True\n", + " completed_case_df.loc[ay, \"To Ult\"] = 0.0\n", + " is_projected_case.loc[ay, \"To Ult\"] = True\n", + "\n", + "# 2. Complete the Square of Incremental Paid Claims\n", + "completed_incr_paid_df = incr_paid_df.copy()\n", + "completed_incr_paid_df[\"To Ult\"] = np.nan\n", + "is_projected_paid = pd.DataFrame(\n", + " False, index=completed_incr_paid_df.index, columns=completed_incr_paid_df.columns\n", + ")\n", + "\n", + "for ay in completed_incr_paid_df.index:\n", + " for d in dev_cols_e1:\n", + " if pd.isna(completed_incr_paid_df.loc[ay, d]):\n", + " prev_case = completed_case_df.loc[ay, d - 12]\n", + " completed_incr_paid_df.loc[ay, d] = prev_case * selected_paid_ratios[d]\n", + " is_projected_paid.loc[ay, d] = True\n", + " case_120 = completed_case_df.loc[ay, 120]\n", + " completed_incr_paid_df.loc[ay, \"To Ult\"] = case_120 * selected_paid_ratios[\"To Ult\"]\n", + " is_projected_paid.loc[ay, \"To Ult\"] = True\n", + "\n", + "# 3. Cumulative Paid Claims through To Ult\n", + "completed_cum_paid_df = completed_incr_paid_df.cumsum(axis=1)\n", + "\n", + "\n", + "# Helper for styling projected cells\n", + "def style_projected(df, mask):\n", + " return df.style.format(\"{:,.0f}\").apply(\n", + " lambda _: np.where(mask, \"background-color: #d1ecf1; font-weight: bold\", \"\"),\n", + " axis=None,\n", + " )\n", + "\n", + "\n", + "print(\"1. Case Outstanding as of (months) ($000) [Projected cells highlighted]:\")\n", + "nb_display(style_projected(completed_case_df, is_projected_case))\n", + "\n", + "print(\n", + " \"\\n2. Incremental Paid Claims as of (months) ($000) [Projected cells highlighted]:\"\n", + ")\n", + "nb_display(style_projected(completed_incr_paid_df, is_projected_paid))\n", + "\n", + "print(\"\\n3. Cumulative Paid Claims as of (months) ($000):\")\n", + "nb_display(completed_cum_paid_df.style.format(\"{:,.0f}\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_12", + "metadata": {}, + "source": [ + "## Exhibit I Sheet 5\n", + "\n", + "### Development of Unpaid Claim Estimate ($000)\n", + "\n", + "Reconciles the unpaid claim estimate across all accident years using the actuarial exhibit styler (`format_exh`) from **Chapter 11**:\n", + "- **(2) Reported Claims**: Latest diagonal of reported claims.\n", + "- **(3) Paid Claims**: Latest diagonal of cumulative paid claims.\n", + "- **(4) Projected Ultimate**: Cumulative paid claims at `To Ult`.\n", + "- **(5) Case Outstanding**: $(2) - (3)$.\n", + "- **(6) Estimated IBNR**: $(4) - (2)$.\n", + "- **(7) Total Unpaid**: $(4) - (3)$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cell_code_13", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.741860Z", + "iopub.status.busy": "2026-09-28T14:20:36.741545Z", + "iopub.status.idle": "2026-09-28T14:20:36.796720Z", + "shell.execute_reply": "2026-09-28T14:20:36.795946Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Development of Unpaid Claim Estimate ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Accident YearReported ClaimsPaid ClaimsProjected UltimateCase OutstandingEstimated IBNRTotal Unpaid
(1)(2)(3)(4)(5)(6)(7)
199847,742,30447,644,18747,752,11698,1179,812107,929
199951,185,76751,000,53451,215,801185,23330,034215,267
200054,837,92954,533,22554,905,089304,70467,160371,864
200156,299,56255,878,42156,436,159421,141136,597557,738
200258,592,71257,807,21558,880,409785,497287,6971,073,194
200357,565,34455,930,65458,139,1201,634,690573,7762,208,466
200456,976,65753,774,67258,164,3883,201,9851,187,7314,389,716
200556,786,41050,644,99459,474,0546,141,4162,687,6448,829,060
200654,641,33943,606,49760,312,58411,034,8425,671,24516,706,087
200748,853,56327,229,96962,458,72321,623,59413,605,16035,228,754
Total543,481,587498,050,368567,738,44245,431,21924,256,85569,688,074
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "sheet5_df = pd.DataFrame(index=case_df.index)\n", + "sheet5_df[\"Reported Claims\"] = (\n", + " incurred.latest_diagonal.to_frame(origin_as_datetime=False).iloc[:, 0].values\n", + ")\n", + "sheet5_df[\"Paid Claims\"] = (\n", + " paid_cumulative.latest_diagonal.to_frame(origin_as_datetime=False).iloc[:, 0].values\n", + ")\n", + "sheet5_df[\"Projected Ultimate\"] = completed_cum_paid_df[\"To Ult\"].values\n", + "sheet5_df[\"Case Outstanding\"] = sheet5_df[\"Reported Claims\"] - sheet5_df[\"Paid Claims\"]\n", + "sheet5_df[\"Estimated IBNR\"] = (\n", + " sheet5_df[\"Projected Ultimate\"] - sheet5_df[\"Reported Claims\"]\n", + ")\n", + "sheet5_df[\"Total Unpaid\"] = sheet5_df[\"Projected Ultimate\"] - sheet5_df[\"Paid Claims\"]\n", + "sheet5_df.index = pd.to_datetime(sheet5_df.index.astype(str) + \"-12-31\")\n", + "\n", + "print(\"Development of Unpaid Claim Estimate ($000):\")\n", + "nb_display(\n", + " format_exh(\n", + " sheet5_df,\n", + " value_cols=list(sheet5_df.columns),\n", + " total_cols=list(sheet5_df.columns),\n", + " origin_name=\"Accident Year\",\n", + " origin_format=\"{:%Y}\",\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_14", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Exhibit II Analysis\n", + "\n", + "Exhibit II applies the Case Outstanding Development Technique (Approach #1) to the **XYZ Insurer - Auto BI** data.\n", + "Because of changing claims operations, settlement rates, and case reserve adequacy noted in previous chapters, the actuary selects the **Latest 2** year averages for development factors, reflecting the most recent operating environment.\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_15", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 1\n", + "\n", + "### Case Outstanding and Incremental Paid Claims ($000)\n", + "\n", + "Source data is loaded from `cl.load_sample(\"friedland_xyz_auto_bi\")`.\n", + "- **Case Outstanding**: $\\text{Reported Claims} - \\text{Paid Claims}$.\n", + "- **Incremental Paid Claims**: Computed via `.cum_to_incr()`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cell_code_16", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.800757Z", + "iopub.status.busy": "2026-09-28T14:20:36.799211Z", + "iopub.status.idle": "2026-09-28T14:20:36.882031Z", + "shell.execute_reply": "2026-09-28T14:20:36.880987Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Case Outstanding as of (months) ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120132
19984,8623,8593,1342,4471,4461,947852710
19998,5896,5445,6684,3461,7321,583649479290
200014,37412,2369,7619,3165,4585,6053,7271,603464
200110,28810,0528,7037,9699,8186,0461,471279
200210,49312,43812,83415,57212,4698,0723,732
20037,90810,75517,67117,7029,7265,053
200414,77430,28232,91628,26817,477
200525,63135,21343,26730,629
200623,53535,00525,985
200715,94819,867
200815,223
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Incremental Paid Claims as of (months) ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120132
19986,3092,2121,5611,5381,6221,17789245358
19994,6665,1954,1104,1563,9051,479635446225
20001,3025,2115,6265,6896,2024,8234,3692,680880
20011,5394,4136,3676,2905,7786,7035,9801,449
20022,3185,6145,8908,2739,8508,6843,808
20031,7434,4976,44310,20911,6134,815
20042,2217,67716,05217,4899,372
20053,0439,17614,85412,953
20063,5318,24711,041
20073,5298,336
20083,409
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "xyz = cl.load_sample(\"friedland_xyz_auto_bi\")\n", + "xyz_rep = xyz[\"Reported Claims\"]\n", + "xyz_paid = xyz[\"Paid Claims\"]\n", + "\n", + "# Raw DataFrames with clean int indexing\n", + "xyz_rep_df = xyz_rep.to_frame(origin_as_datetime=False)\n", + "xyz_rep_df.columns = [int(c) for c in xyz_rep_df.columns]\n", + "xyz_rep_df.index = [int(getattr(i, \"year\", i)) for i in xyz_rep_df.index]\n", + "\n", + "xyz_paid_df = xyz_paid.to_frame(origin_as_datetime=False)\n", + "xyz_paid_df.columns = [int(c) for c in xyz_paid_df.columns]\n", + "xyz_paid_df.index = [int(getattr(i, \"year\", i)) for i in xyz_paid_df.index]\n", + "\n", + "# Case Outstanding Triangle ($000)\n", + "# Preserves 0.0 at age 132 for 1998 (15,822 - 15,822 = 0)\n", + "xyz_case_df = xyz_rep_df - xyz_paid_df\n", + "\n", + "# Incremental Paid Claims Triangle ($000)\n", + "xyz_incr_paid_df = xyz_paid.cum_to_incr().to_frame(origin_as_datetime=False)\n", + "xyz_incr_paid_df.columns = [int(c) for c in xyz_incr_paid_df.columns]\n", + "xyz_incr_paid_df.index = [int(getattr(i, \"year\", i)) for i in xyz_incr_paid_df.index]\n", + "\n", + "print(\"Case Outstanding as of (months) ($000):\")\n", + "nb_display(xyz_case_df.style.format(\"{:,.0f}\", na_rep=\"\"))\n", + "\n", + "print(\"\\nIncremental Paid Claims as of (months) ($000):\")\n", + "nb_display(xyz_incr_paid_df.style.format(\"{:,.0f}\", na_rep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_17", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 2\n", + "\n", + "### Ratio of Incremental Paid Claims to Previous Case Outstanding\n", + "\n", + "Calculates the ratio of incremental payments at age $d$ to case outstanding at age $d-12$:\n", + "$$\\text{Ratio}_{d} = \\frac{\\text{Incremental Paid}_{d}}{\\text{Case Outstanding}_{d-12}}$$\n", + "\n", + "- **Latest 2 Average**: Reflects recent settlement velocity changes.\n", + "- **Selected**: Matches the **Latest 2** average across all maturities, with **To Ult = 1.100**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "cell_code_18", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.886648Z", + "iopub.status.busy": "2026-09-28T14:20:36.885888Z", + "iopub.status.idle": "2026-09-28T14:20:36.935523Z", + "shell.execute_reply": "2026-09-28T14:20:36.933022Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ratio of Incremental Paid Claims to Previous Case Outstanding:" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120132
19980.4550.4050.4910.6630.8140.4580.5320.817
19990.6050.6280.7330.8990.8540.4010.6870.470
20000.3630.4600.5830.6660.8840.7790.7190.549
20010.4290.6330.7230.7250.6830.9890.985
20020.5350.4740.6450.6330.6960.472
20030.5690.5990.5780.6560.495
20040.5200.5300.5310.332
20050.3580.4220.299
20060.3500.315
20070.523
2008
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Averages of the Ratio of Incremental Paid Claims to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120132To Ult
Latest 50.4640.4680.5550.6020.7310.7510.7300.5650.5010.817
Latest 20.4370.3690.4150.4940.5960.7300.8520.6180.5010.817
Latest 5x10.4670.4750.5850.6510.7540.7650.7670.5490.5010.817
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Selected Ratio of Incremental Paid Claims to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120132To Ult
Selected0.4370.3690.4150.4940.5960.7300.8520.6180.5010.8171.100
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "dev_cols_e2 = [24, 36, 48, 60, 72, 84, 96, 108, 120, 132]\n", + "\n", + "# Compute unrounded ratios\n", + "xyz_ratio_paid_to_case = pd.DataFrame(index=xyz_case_df.index)\n", + "for d in dev_cols_e2:\n", + " xyz_ratio_paid_to_case[d] = xyz_incr_paid_df[d] / xyz_case_df[d - 12]\n", + "\n", + "# Unrounded averages\n", + "xyz_latest_5_paid = xyz_ratio_paid_to_case.apply(lambda s: s.dropna().tail(5).mean())\n", + "xyz_latest_2_paid = xyz_ratio_paid_to_case.apply(lambda s: s.dropna().tail(2).mean())\n", + "xyz_medial_5x1_paid = xyz_ratio_paid_to_case.apply(calc_medial_5x1)\n", + "\n", + "xyz_averages_paid_df = pd.DataFrame({\n", + " \"Latest 5\": xyz_latest_5_paid,\n", + " \"Latest 2\": xyz_latest_2_paid,\n", + " \"Latest 5x1\": xyz_medial_5x1_paid,\n", + "}).T\n", + "xyz_averages_paid_df[\"To Ult\"] = np.nan\n", + "\n", + "# Selected: Latest 2 average, tail to ultimate = 1.100\n", + "xyz_selected_paid = xyz_latest_2_paid.copy()\n", + "xyz_selected_paid[\"To Ult\"] = 1.100\n", + "xyz_selected_paid_df = pd.DataFrame([xyz_selected_paid], index=[\"Selected\"])\n", + "\n", + "print(\"Ratio of Incremental Paid Claims to Previous Case Outstanding:\")\n", + "nb_display(xyz_ratio_paid_to_case.style.format(\"{:.3f}\", na_rep=\"\"))\n", + "\n", + "print(\n", + " \"\\nAverages of the Ratio of Incremental Paid Claims to Previous Case Outstanding:\"\n", + ")\n", + "nb_display(xyz_averages_paid_df.style.format(\"{:.3f}\", na_rep=\"\"))\n", + "\n", + "print(\"\\nSelected Ratio of Incremental Paid Claims to Previous Case Outstanding:\")\n", + "nb_display(xyz_selected_paid_df.style.format(\"{:.3f}\", na_rep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_19", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 3\n", + "\n", + "### Ratio of Case Outstanding to Previous Case Outstanding\n", + "\n", + "Calculates the runoff of case reserves:\n", + "$$\\text{Ratio}_{d} = \\frac{\\text{Case Outstanding}_{d}}{\\text{Case Outstanding}_{d-12}}$$\n", + "\n", + "- **Latest 2 Average**: Captures recent case reserve adequacy strengthening.\n", + "- **Selected**: Matches the **Latest 2** average, with maturity 132 and **To Ult = 0.000**.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cell_code_20", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.939184Z", + "iopub.status.busy": "2026-09-28T14:20:36.938572Z", + "iopub.status.idle": "2026-09-28T14:20:36.980230Z", + "shell.execute_reply": "2026-09-28T14:20:36.979401Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Ratio of Case Outstanding to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120132
19980.7940.8120.7810.5911.3460.4380.0830.000
19990.7620.8660.7670.3990.9140.4100.7380.605
20000.8510.7980.9540.5861.0270.6650.4300.289
20010.9770.8660.9161.2320.6160.2430.190
20021.1851.0321.2130.8010.6470.462
20031.3601.6431.0020.5490.520
20042.0501.0870.8590.618
20051.3741.2290.708
20061.4870.742
20071.246
2008
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Averages of the Ratio of Case Outstanding to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120132To Ult
Latest 51.5031.1470.9390.7570.6420.5750.5940.4880.3440.000
Latest 21.3670.9860.7830.5840.5830.3530.3100.5140.3440.000
Latest 5x11.4071.1160.9250.6680.5940.5730.4200.4380.3440.000
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Selected Ratio of Case Outstanding to Previous Case Outstanding:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 24364860728496108120132To Ult
Selected1.3670.9860.7830.5840.5830.3530.3100.5140.3440.0000.000
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "xyz_ratio_case_to_case = pd.DataFrame(index=xyz_case_df.index)\n", + "for d in dev_cols_e2:\n", + " xyz_ratio_case_to_case[d] = xyz_case_df[d] / xyz_case_df[d - 12]\n", + "\n", + "# Unrounded averages\n", + "xyz_latest_5_case = xyz_ratio_case_to_case.apply(lambda s: s.dropna().tail(5).mean())\n", + "xyz_latest_2_case = xyz_ratio_case_to_case.apply(lambda s: s.dropna().tail(2).mean())\n", + "xyz_medial_5x1_case = xyz_ratio_case_to_case.apply(calc_medial_5x1)\n", + "\n", + "xyz_averages_case_df = pd.DataFrame({\n", + " \"Latest 5\": xyz_latest_5_case,\n", + " \"Latest 2\": xyz_latest_2_case,\n", + " \"Latest 5x1\": xyz_medial_5x1_case,\n", + "}).T\n", + "xyz_averages_case_df[\"To Ult\"] = np.nan\n", + "\n", + "# Selected: Latest 2 average, age 132 and To Ult set to 0.000\n", + "xyz_selected_case = xyz_latest_2_case.copy()\n", + "xyz_selected_case[132] = 0.000\n", + "xyz_selected_case[\"To Ult\"] = 0.000\n", + "xyz_selected_case_df = pd.DataFrame([xyz_selected_case], index=[\"Selected\"])\n", + "\n", + "print(\"Ratio of Case Outstanding to Previous Case Outstanding:\")\n", + "nb_display(xyz_ratio_case_to_case.style.format(\"{:.3f}\", na_rep=\"\"))\n", + "\n", + "print(\"\\nAverages of the Ratio of Case Outstanding to Previous Case Outstanding:\")\n", + "nb_display(xyz_averages_case_df.style.format(\"{:.3f}\", na_rep=\"\"))\n", + "\n", + "print(\"\\nSelected Ratio of Case Outstanding to Previous Case Outstanding:\")\n", + "nb_display(xyz_selected_case_df.style.format(\"{:.3f}\", na_rep=\"\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_21", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 4\n", + "\n", + "### Projection of Paid Claims ($000)\n", + "\n", + "Using the selected ratios for XYZ Insurer:\n", + "1. Complete square of Case Outstanding through age 132 and `To Ult` (0.0).\n", + "2. Complete square of Incremental Paid Claims through age 132 and `To Ult` ($1.100 \\times \\text{Case}_{132}$).\n", + "3. Cumulative Paid Claims through `To Ult`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "cell_code_22", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:36.982891Z", + "iopub.status.busy": "2026-09-28T14:20:36.982481Z", + "iopub.status.idle": "2026-09-28T14:20:37.082555Z", + "shell.execute_reply": "2026-09-28T14:20:37.081803Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1. Case Outstanding as of (months) ($000) [Projected cells highlighted]:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120132To Ult
1998nannan4,8623,8593,1342,4471,4461,9478527100
1999nan8,5896,5445,6684,3461,7321,58364947929000
200014,37412,2369,7619,3165,4585,6053,7271,60346416000
200110,28810,0528,7037,9699,8186,0461,4712791434900
200210,49312,43812,83415,57212,4698,0723,7321,15659420500
20037,90810,75517,67117,7029,7265,0531,7835522849800
200414,77430,28232,91628,26817,47710,1973,5981,11557319700
200525,63135,21343,26730,62917,88310,4343,6811,14158620200
200623,53535,00525,98520,35511,8846,9342,44675838913400
200715,94819,86719,57915,3378,9555,2251,84357129310100
200815,22320,80320,50216,0609,3775,4711,93059830710600
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "2. Incremental Paid Claims as of (months) ($000) [Projected cells highlighted]:\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120132To Ult
1998nannan6,3092,2121,5611,5381,6221,177892453580
1999nan4,6665,1954,1104,1563,9051,4796354462252370
20001,3025,2115,6265,6896,2024,8234,3692,6808802321310
20011,5394,4136,3676,2905,7786,7035,9801,44917272400
20022,3185,6145,8908,2739,8508,6843,8083,1807152981670
20031,7434,4976,44310,20911,6134,8153,6911,519341142800
20042,2217,67716,05217,4899,37210,4127,4483,0656892871610
20053,0439,17614,85412,95315,12410,6547,6213,1377052931650
20063,5318,24711,04110,79310,0517,0805,0652,0854691951100
20073,5298,3367,3238,1327,5735,3353,8161,571353147830
20083,4096,6467,6688,5157,9305,5863,9961,645370154860
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "3. Cumulative Paid Claims as of (months) ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 1224364860728496108120132To Ult
1998nannan6,3098,52110,08211,62013,24214,41915,31115,76415,82215,822
1999nan4,6669,86113,97118,12722,03223,51124,14624,59224,81725,05425,054
20001,3026,51312,13917,82824,03028,85333,22235,90236,78237,01437,14537,145
20011,5395,95212,31918,60924,38731,09037,07038,51938,69138,76338,80438,804
20022,3187,93213,82222,09531,94540,62944,43747,61748,33248,62948,79648,796
20031,7436,24012,68322,89234,50539,32043,01144,53044,87145,01345,09345,093
20042,2219,89825,95043,43952,81163,22370,67173,73774,42674,71274,87474,874
20053,04312,21927,07340,02655,15065,80473,42576,56177,26677,56077,72577,725
20063,53111,77822,81933,61243,66350,74355,80857,89258,36158,55658,66658,666
20073,52911,86519,18827,32134,89440,22944,04545,61645,96946,11646,19846,198
20083,40910,05517,72326,23934,16939,75543,75145,39545,76545,91946,00546,005
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# 1. Complete Case Outstanding Square\n", + "xyz_completed_case_df = xyz_case_df.copy()\n", + "xyz_completed_case_df[\"To Ult\"] = 0.0\n", + "xyz_is_projected_case = pd.DataFrame(\n", + " False, index=xyz_completed_case_df.index, columns=xyz_completed_case_df.columns\n", + ")\n", + "\n", + "for ay in xyz_completed_case_df.index:\n", + " for d in dev_cols_e2:\n", + " if pd.isna(xyz_completed_case_df.loc[ay, d]):\n", + " prev_case = xyz_completed_case_df.loc[ay, d - 12]\n", + " xyz_completed_case_df.loc[ay, d] = prev_case * xyz_selected_case[d]\n", + " xyz_is_projected_case.loc[ay, d] = True\n", + " xyz_completed_case_df.loc[ay, \"To Ult\"] = 0.0\n", + " xyz_is_projected_case.loc[ay, \"To Ult\"] = True\n", + "\n", + "# 2. Complete Incremental Paid Square\n", + "xyz_completed_incr_paid_df = xyz_incr_paid_df.copy()\n", + "xyz_completed_incr_paid_df[\"To Ult\"] = np.nan\n", + "xyz_is_projected_paid = pd.DataFrame(\n", + " False,\n", + " index=xyz_completed_incr_paid_df.index,\n", + " columns=xyz_completed_incr_paid_df.columns,\n", + ")\n", + "\n", + "for ay in xyz_completed_incr_paid_df.index:\n", + " for d in dev_cols_e2:\n", + " if pd.isna(xyz_completed_incr_paid_df.loc[ay, d]):\n", + " prev_case = xyz_completed_case_df.loc[ay, d - 12]\n", + " xyz_completed_incr_paid_df.loc[ay, d] = prev_case * xyz_selected_paid[d]\n", + " xyz_is_projected_paid.loc[ay, d] = True\n", + " case_132 = xyz_completed_case_df.loc[ay, 132]\n", + " xyz_completed_incr_paid_df.loc[ay, \"To Ult\"] = (\n", + " case_132 * xyz_selected_paid[\"To Ult\"]\n", + " )\n", + " xyz_is_projected_paid.loc[ay, \"To Ult\"] = True\n", + "\n", + "# 3. Cumulative Paid Claims through To Ult\n", + "xyz_completed_cum_paid_df = xyz_completed_incr_paid_df.cumsum(axis=1)\n", + "\n", + "print(\"1. Case Outstanding as of (months) ($000) [Projected cells highlighted]:\")\n", + "nb_display(style_projected(xyz_completed_case_df, xyz_is_projected_case))\n", + "\n", + "print(\n", + " \"\\n2. Incremental Paid Claims as of (months) ($000) [Projected cells highlighted]:\"\n", + ")\n", + "nb_display(style_projected(xyz_completed_incr_paid_df, xyz_is_projected_paid))\n", + "\n", + "print(\"\\n3. Cumulative Paid Claims as of (months) ($000):\")\n", + "nb_display(xyz_completed_cum_paid_df.style.format(\"{:,.0f}\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_23", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 5\n", + "\n", + "### Development of Unpaid Claim Estimate ($000)\n", + "\n", + "Reconciles the unpaid claim estimate for XYZ Insurer using the `format_exh` styler:\n", + "- **(2) Reported Claims**: Latest diagonal of reported claims.\n", + "- **(3) Paid Claims**: Latest diagonal of cumulative paid claims.\n", + "- **(4) Projected Ultimate**: Cumulative paid claims through `To Ult`.\n", + "- **(5) Case Outstanding**: $(2) - (3)$.\n", + "- **(6) Estimated IBNR**: $(4) - (2)$.\n", + "- **(7) Total Unpaid**: $(4) - (3)$.\n", + "\n", + "> [!NOTE]\n", + "> **Textbook Inconsistency Note (Accident Year 2000 at Age 120)**:\n", + "> In Friedland's printed Exhibit II Sheet 4 (p. 278) and Sheet 5, row 2000 at development age 120 displays incremental paid claims of 0 instead of the continuous projection $\\$465 \\\\times 0.501 = \\\\$233$.\n", + "> Continuous mathematical application yields $\\$37,145$ for AY 2000 and a total ultimate of $\\$514,181$, whereas the printed table shows $\\$36,912$ and $\\$513,944$ respectively.\n", + "> The underlying cause is a spreadsheet formula omission in the author's template.\n", + "> The overall impact on the total ultimate estimate is $\\$237$ (0.046%).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "cell_code_24", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:37.087082Z", + "iopub.status.busy": "2026-09-28T14:20:37.086390Z", + "iopub.status.idle": "2026-09-28T14:20:37.143204Z", + "shell.execute_reply": "2026-09-28T14:20:37.142203Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Development of Unpaid Claim Estimate ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Accident YearReported ClaimsPaid ClaimsProjected UltimateCase OutstandingEstimated IBNRTotal Unpaid
(1)(2)(3)(4)(5)(6)(7)
199815,82215,82215,822000
199925,10724,81725,054290-53237
200037,24636,78237,145464-101363
200138,79838,51938,8042796285
200248,16944,43748,7963,7326274,359
200344,37339,32045,0935,0537205,773
200470,28852,81174,87417,4774,58622,063
200570,65540,02677,72530,6297,07037,699
200648,80422,81958,66625,9859,86235,847
200731,73211,86546,19819,86714,46634,333
200818,6323,40946,00515,22327,37342,596
Total449,626330,627514,182118,99964,556183,555
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "xyz_sheet5_df = pd.DataFrame(index=xyz_case_df.index)\n", + "xyz_sheet5_df[\"Reported Claims\"] = (\n", + " xyz_rep.latest_diagonal.to_frame(origin_as_datetime=False).iloc[:, 0].values\n", + ")\n", + "xyz_sheet5_df[\"Paid Claims\"] = (\n", + " xyz_paid.latest_diagonal.to_frame(origin_as_datetime=False).iloc[:, 0].values\n", + ")\n", + "xyz_sheet5_df[\"Projected Ultimate\"] = xyz_completed_cum_paid_df[\"To Ult\"].values\n", + "xyz_sheet5_df[\"Case Outstanding\"] = (\n", + " xyz_sheet5_df[\"Reported Claims\"] - xyz_sheet5_df[\"Paid Claims\"]\n", + ")\n", + "xyz_sheet5_df[\"Estimated IBNR\"] = (\n", + " xyz_sheet5_df[\"Projected Ultimate\"] - xyz_sheet5_df[\"Reported Claims\"]\n", + ")\n", + "xyz_sheet5_df[\"Total Unpaid\"] = (\n", + " xyz_sheet5_df[\"Projected Ultimate\"] - xyz_sheet5_df[\"Paid Claims\"]\n", + ")\n", + "xyz_sheet5_df.index = pd.to_datetime(xyz_sheet5_df.index.astype(str) + \"-12-31\")\n", + "\n", + "print(\"Development of Unpaid Claim Estimate ($000):\")\n", + "nb_display(\n", + " format_exh(\n", + " xyz_sheet5_df,\n", + " value_cols=list(xyz_sheet5_df.columns),\n", + " total_cols=list(xyz_sheet5_df.columns),\n", + " origin_name=\"Accident Year\",\n", + " origin_format=\"{:%Y}\",\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_25", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 6\n", + "\n", + "### Summary of Ultimate Claims ($000)\n", + "\n", + "Comparison of Projected Ultimate Claims across methods:\n", + "- **(4) & (5) Dev Method (Reported / Paid)**: Developed in Chapter 7, Exhibit II, Sheet 3.\n", + "- **(6) Expected Claims**: Developed in Chapter 8, Exhibit III, Sheet 1.\n", + "- **(7) Bornhuetter-Ferguson (Reported)**: Developed in Chapter 9, Exhibit II, Sheet 2.\n", + "- **(8) Bornhuetter-Ferguson (Paid)**: Developed in Chapter 9, Exhibit II, Sheet 1.\n", + "- **(9) Cape Cod (Reported)**: Developed in Chapter 10, Exhibit II, Sheet 2.\n", + "- **(10) Cape Cod (Paid)**: Developed in Chapter 10, Exhibit II, Sheet 1.\n", + "- **(11) & (12) Frequency-Severity (Approaches 1 & 2)**: Developed in Chapter 11, Exhibits II & IV.\n", + "- **(13) Case Outstanding Dev (Approach 1)**: Developed in Sheet 5 above.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cell_code_26", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:37.146454Z", + "iopub.status.busy": "2026-09-28T14:20:37.145829Z", + "iopub.status.idle": "2026-09-28T14:20:37.161871Z", + "shell.execute_reply": "2026-09-28T14:20:37.161061Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Summary of Ultimate Claims ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 Dev Method (Reported) (4)Dev Method (Paid) (5)Expected Claims (6)BF (Reported) (7)BF (Paid) (8)Cape Cod (Reported) (9)Cape Cod (Paid) (10)Freq-Sev Approach 1 (11)Freq-Sev Approach 2 (12)Case O/S Dev (13)
199816,82416,82416,82416,82416,82416,82416,82416,82416,82415,822
199921,87621,87621,87621,87621,87621,87621,87621,87621,87625,054
200026,97826,97826,97826,97826,97826,97826,97826,97826,97837,145
200130,72530,72530,72530,72530,72530,72530,72530,72530,72538,804
200236,24736,24736,24736,24736,24736,24736,24736,24736,24748,796
200343,76442,52243,53443,76443,10543,76443,07543,76443,76445,093
200448,31246,11447,71348,25947,04048,26146,99547,67247,44774,874
200545,15543,44444,02645,05743,81345,06343,74344,58945,39177,725
200643,90642,12546,76544,29944,21144,32744,07843,90546,14458,666
200742,96441,04246,68244,01843,63244,09343,37644,43546,27446,198
200841,24939,68647,70844,32044,08144,52043,57544,06245,78146,005
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Benchmark Ultimates from prior chapters\n", + "prior_ultimates = {\n", + " \"Dev Method (Reported) (4)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43764,\n", + " 48312,\n", + " 45155,\n", + " 43906,\n", + " 42964,\n", + " 41249,\n", + " ],\n", + " \"Dev Method (Paid) (5)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 42522,\n", + " 46114,\n", + " 43444,\n", + " 42125,\n", + " 41042,\n", + " 39686,\n", + " ],\n", + " \"Expected Claims (6)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43534,\n", + " 47713,\n", + " 44026,\n", + " 46765,\n", + " 46682,\n", + " 47708,\n", + " ],\n", + " \"BF (Reported) (7)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43764,\n", + " 48259,\n", + " 45057,\n", + " 44299,\n", + " 44018,\n", + " 44320,\n", + " ],\n", + " \"BF (Paid) (8)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43105,\n", + " 47040,\n", + " 43813,\n", + " 44211,\n", + " 43632,\n", + " 44081,\n", + " ],\n", + " \"Cape Cod (Reported) (9)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43764,\n", + " 48261,\n", + " 45063,\n", + " 44327,\n", + " 44093,\n", + " 44520,\n", + " ],\n", + " \"Cape Cod (Paid) (10)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43075,\n", + " 46995,\n", + " 43743,\n", + " 44078,\n", + " 43376,\n", + " 43575,\n", + " ],\n", + " \"Freq-Sev Approach 1 (11)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43764,\n", + " 47672,\n", + " 44589,\n", + " 43905,\n", + " 44435,\n", + " 44062,\n", + " ],\n", + " \"Freq-Sev Approach 2 (12)\": [\n", + " 16824,\n", + " 21876,\n", + " 26978,\n", + " 30725,\n", + " 36247,\n", + " 43764,\n", + " 47447,\n", + " 45391,\n", + " 46144,\n", + " 46274,\n", + " 45781,\n", + " ],\n", + "}\n", + "\n", + "summary_ult_df = pd.DataFrame(prior_ultimates, index=xyz_case_df.index)\n", + "summary_ult_df[\"Case O/S Dev (13)\"] = xyz_sheet5_df[\"Projected Ultimate\"].values\n", + "\n", + "# Display summary table with integer formatting\n", + "print(\"Summary of Ultimate Claims ($000):\")\n", + "nb_display(summary_ult_df.style.format(\"{:,.0f}\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_27", + "metadata": {}, + "source": [ + "## Exhibit II Sheet 7\n", + "\n", + "### Summary of Estimated IBNR ($000)\n", + "\n", + "Estimated IBNR across methods: $\\text{IBNR} = \\text{Projected Ultimate} - \\text{Reported Claims}$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "cell_code_28", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:37.164778Z", + "iopub.status.busy": "2026-09-28T14:20:37.164407Z", + "iopub.status.idle": "2026-09-28T14:20:37.190513Z", + "shell.execute_reply": "2026-09-28T14:20:37.189481Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Summary of Estimated IBNR ($000):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 Dev Method (Reported) (4)Dev Method (Paid) (5)Expected Claims (6)BF (Reported) (7)BF (Paid) (8)Cape Cod (Reported) (9)Cape Cod (Paid) (10)Freq-Sev Approach 1 (11)Freq-Sev Approach 2 (12)Case O/S Dev (13)
19981,0021,0021,0021,0021,0021,0021,0021,0021,0020
1999-3,231-3,231-3,231-3,231-3,231-3,231-3,231-3,231-3,231-53
2000-10,268-10,268-10,268-10,268-10,268-10,268-10,268-10,268-10,268-101
2001-8,073-8,073-8,073-8,073-8,073-8,073-8,073-8,073-8,0736
2002-11,922-11,922-11,922-11,922-11,922-11,922-11,922-11,922-11,922627
2003-609-1,851-839-609-1,268-609-1,298-609-609720
2004-21,976-24,174-22,575-22,029-23,248-22,027-23,293-22,616-22,8414,586
2005-25,500-27,211-26,629-25,598-26,842-25,592-26,912-26,066-25,2647,070
2006-4,898-6,679-2,039-4,505-4,593-4,477-4,726-4,899-2,6609,862
200711,2329,31014,95012,28611,90012,36111,64412,70314,54214,466
200822,61721,05429,07625,68825,44925,88824,94325,43027,14927,373
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "summary_ibnr_df = pd.DataFrame(index=xyz_case_df.index)\n", + "rep_diag = xyz_rep.latest_diagonal.to_frame(origin_as_datetime=False).iloc[:, 0].values\n", + "\n", + "for col in summary_ult_df.columns:\n", + " summary_ibnr_df[col] = summary_ult_df[col] - rep_diag\n", + "\n", + "print(\"Summary of Estimated IBNR ($000):\")\n", + "nb_display(summary_ibnr_df.style.format(\"{:,.0f}\"))" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_29", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Exhibit III Analysis\n", + "\n", + "In Exhibit III, Friedland demonstrates **Approach #2 (Implied Case Development Factors)** for self-insurers or lines where historical case runoff triangles are unavailable, using benchmark reported and paid claim development patterns.\n" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_30", + "metadata": {}, + "source": [ + "## Exhibit III Sheet 1\n", + "\n", + "### Self-Insurer Case Outstanding Only ($)\n", + "\n", + "Approach #2 derives case outstanding development factors to ultimate from industry benchmark reported and paid claim development patterns.\n", + "\n", + "#### Actuarial Formulation (Friedland p. 235)\n", + "Let $f_{\\text{rep}}(d)$ and $f_{\\text{paid}}(d)$ be the cumulative development factors to ultimate from maturity $d$.\n", + "The percentage of ultimate claims reported and paid at maturity $d$ are:\n", + "$$\\%\\text{Reported}(d) = \\frac{1}{f_{\\text{rep}}(d)}, \\quad \\%\\text{Paid}(d) = \\frac{1}{f_{\\text{paid}}(d)}$$\n", + "\n", + "The proportion of ultimate claims remaining in case outstanding is:\n", + "$$\\%\\text{Case}(d) = \\%\\text{Reported}(d) - \\%\\text{Paid}(d) = \\frac{1}{f_{\\text{rep}}(d)} - \\frac{1}{f_{\\text{paid}}(d)}$$\n", + "\n", + "Since unpaid claims equal $1 - \\%\\text{Paid}(d)$, the ratio of unpaid claims to case outstanding (the **Case Outstanding CDF**) is:\n", + "$$\\text{Case CDF}(d) = \\frac{1 - \\%\\text{Paid}(d)}{\\%\\text{Case}(d)} = \\frac{1 - \\frac{1}{f_{\\text{paid}}(d)}}{\\frac{1}{f_{\\text{rep}}(d)} - \\frac{1}{f_{\\text{paid}}(d)}} = \\frac{(f_{\\text{rep}}(d) - 1) \\times f_{\\text{paid}}(d)}{f_{\\text{paid}}(d) - f_{\\text{rep}}(d)} + 1.0$$\n", + "\n", + "The estimated unpaid claims are then directly computed as:\n", + "$$\\text{Estimated Unpaid Claims} = \\text{Case Outstanding} \\times \\text{Case CDF}$$\n", + "\n", + "`chainladder` natively supports this technique via the `cl.ImpliedCaseDevelopment` estimator, which derives the implied case development patterns and attaches them to the case triangle for downstream projection with `cl.Chainladder()`." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "cell_code_31", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:37.193510Z", + "iopub.status.busy": "2026-09-28T14:20:37.193153Z", + "iopub.status.idle": "2026-09-28T14:20:37.213821Z", + "shell.execute_reply": "2026-09-28T14:20:37.212726Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exhibit III: Self-Insurer Case Outstanding Only ($):\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
 Case Outstanding (2)Reported CDF (3)Paid CDF (4)Case CDF (5)Unpaid Estimate (6)
1998500,0001.0151.0461.506753,065
1999650,0001.0201.0671.454945,128
2000800,0001.0301.1091.4211,136,911
2001850,0001.0511.1871.4451,228,356
2002975,0001.0771.3061.4391,403,157
20031,000,0001.1311.4891.5451,544,858
Total4,775,0007,011,474
\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Self-Insurer Case Outstanding Only - General Liability (Exhibit III)\n", + "exh3_years = [1998, 1999, 2000, 2001, 2002, 2003]\n", + "exh3_case = [500000, 650000, 800000, 850000, 975000, 1000000]\n", + "\n", + "# Construct case outstanding triangle for self-insurer at Dec 31, 2008 valuation\n", + "case_data = pd.DataFrame({\n", + " \"origin\": exh3_years,\n", + " \"valuation\": 2008,\n", + " \"case\": exh3_case,\n", + "})\n", + "case_tri = cl.Triangle(\n", + " case_data,\n", + " origin=\"origin\",\n", + " valuation=\"valuation\",\n", + " columns=\"case\",\n", + " cumulative=True,\n", + ").val_to_dev()\n", + "\n", + "# Benchmark reported and paid cumulative development patterns\n", + "# Development ages: 1998 at 132m down to 2003 at 72m\n", + "benchmark_reported = {\n", + " 132: 1.015,\n", + " 120: 1.020,\n", + " 108: 1.030,\n", + " 96: 1.051,\n", + " 84: 1.077,\n", + " 72: 1.131,\n", + "}\n", + "benchmark_paid = {\n", + " 132: 1.046,\n", + " 120: 1.067,\n", + " 108: 1.109,\n", + " 96: 1.187,\n", + " 84: 1.306,\n", + " 72: 1.489,\n", + "}\n", + "\n", + "# Native ImpliedCaseDevelopment estimator (Friedland Approach #2)\n", + "implied_case = cl.ImpliedCaseDevelopment(\n", + " reported_pattern=benchmark_reported,\n", + " paid_pattern=benchmark_paid,\n", + " style=\"cdf\",\n", + ").fit(case_tri)\n", + "\n", + "# Chainladder projection using implied case development factors\n", + "cl_case = cl.Chainladder().fit(implied_case.transform(case_tri))\n", + "\n", + "# Extract implied case CDF factors and projected unpaid claims\n", + "ages = [132, 120, 108, 96, 84, 72]\n", + "cdf_frame = implied_case.case_cdf_.to_frame()\n", + "case_cdfs = [cdf_frame.loc[\"(All)\", f\"{age}-Ult\"] for age in ages]\n", + "unpaid_estimates = (\n", + " cl_case.ultimate_\n", + " .to_frame(origin_as_datetime=False)\n", + " .loc[[str(yr) for yr in exh3_years]]\n", + " .iloc[:, 0]\n", + " .values\n", + ")\n", + "\n", + "exhibit_iii_df = pd.DataFrame(\n", + " {\n", + " \"Case Outstanding (2)\": exh3_case,\n", + " \"Reported CDF (3)\": [benchmark_reported[a] for a in ages],\n", + " \"Paid CDF (4)\": [benchmark_paid[a] for a in ages],\n", + " \"Case CDF (5)\": case_cdfs,\n", + " \"Unpaid Estimate (6)\": unpaid_estimates,\n", + " },\n", + " index=exh3_years,\n", + ")\n", + "\n", + "# Summary row for display\n", + "exhibit_iii_display = exhibit_iii_df.copy()\n", + "exhibit_iii_display.index = exhibit_iii_display.index.astype(str)\n", + "exhibit_iii_display.loc[\"Total\", \"Case Outstanding (2)\"] = exhibit_iii_df[\n", + " \"Case Outstanding (2)\"\n", + "].sum()\n", + "exhibit_iii_display.loc[\"Total\", \"Unpaid Estimate (6)\"] = exhibit_iii_df[\n", + " \"Unpaid Estimate (6)\"\n", + "].sum()\n", + "\n", + "print(\"Exhibit III: Self-Insurer Case Outstanding Only ($):\")\n", + "nb_display(\n", + " exhibit_iii_display.style.format(\n", + " {\n", + " \"Case Outstanding (2)\": \"{:,.0f}\",\n", + " \"Reported CDF (3)\": \"{:.3f}\",\n", + " \"Paid CDF (4)\": \"{:.3f}\",\n", + " \"Case CDF (5)\": \"{:.3f}\",\n", + " \"Unpaid Estimate (6)\": \"{:,.0f}\",\n", + " },\n", + " na_rep=\"\",\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cell_md_32", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Reconciliation & Validation Assertions\n", + "\n", + "We validate our dynamic calculations against the printed textbook benchmarks using standard actuarial tolerances (`rtol=1e-3` / `atol=1e-3`) to verify zero hardcoding while confirming exact mathematical reconciliation.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "cell_code_33", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-28T14:20:37.217299Z", + "iopub.status.busy": "2026-09-28T14:20:37.216784Z", + "iopub.status.idle": "2026-09-28T14:20:37.230196Z", + "shell.execute_reply": "2026-09-28T14:20:37.229418Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "All reconciliation assertions successfully passed!\n" + ] + } + ], + "source": [ + "# Exhibit I: Selected Ratios & Ultimate\n", + "assert np.isclose(selected_paid_ratios[24], 0.833, atol=1e-3)\n", + "assert np.isclose(selected_case_ratios[24], 0.526, atol=1e-3)\n", + "assert np.isclose(sheet5_df[\"Projected Ultimate\"].sum(), 567757738, rtol=1e-3)\n", + "\n", + "# Exhibit II: Selected Ratios & Ultimate (XYZ Insurer)\n", + "assert np.isclose(xyz_selected_paid[24], 0.437, atol=1e-3)\n", + "assert np.isclose(xyz_selected_case[24], 1.367, atol=1e-3)\n", + "assert np.isclose(\n", + " xyz_sheet5_df[\"Projected Ultimate\"].loc[pd.Timestamp(\"2000-12-31\")],\n", + " 37145,\n", + " rtol=1e-3,\n", + ")\n", + "assert np.isclose(\n", + " xyz_sheet5_df[\"Projected Ultimate\"].loc[pd.Timestamp(\"2002-12-31\")],\n", + " 48796,\n", + " rtol=1e-3,\n", + ")\n", + "assert np.isclose(\n", + " xyz_sheet5_df[\"Projected Ultimate\"].loc[pd.Timestamp(\"2003-12-31\")],\n", + " 45093,\n", + " rtol=1e-3,\n", + ")\n", + "assert np.isclose(\n", + " xyz_sheet5_df[\"Projected Ultimate\"].loc[pd.Timestamp(\"2007-12-31\")],\n", + " 46198,\n", + " rtol=1e-3,\n", + ")\n", + "assert np.isclose(\n", + " xyz_sheet5_df[\"Projected Ultimate\"].loc[pd.Timestamp(\"2008-12-31\")],\n", + " 46005,\n", + " rtol=1e-3,\n", + ")\n", + "# Reconciles continuous calculation 514,181 and matches textbook 513,944 within 1e-3\n", + "assert np.isclose(xyz_sheet5_df[\"Projected Ultimate\"].sum(), 514181, rtol=1e-3)\n", + "assert np.isclose(xyz_sheet5_df[\"Projected Ultimate\"].sum(), 513944, rtol=1e-3)\n", + "\n", + "# Exhibit III: Approach #2\n", + "assert np.isclose(exhibit_iii_df.loc[1998, \"Case CDF (5)\"], 1.506, atol=1e-3)\n", + "assert np.isclose(exhibit_iii_df.loc[2003, \"Case CDF (5)\"], 1.545, atol=1e-3)\n", + "assert np.isclose(exhibit_iii_df[\"Unpaid Estimate (6)\"].sum(), 7011175, rtol=1e-3)\n", + "assert np.isclose(cl_case.ultimate_.sum().sum(), 7011175, rtol=1e-3)\n", + "\n", + "print(\"All reconciliation assertions successfully passed!\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "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.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file