Repository: lewisfogden/heavylight (language: python)

Issue:
some changes to make_examples function
test the make_examples function

denest the code, it is common to throw errors early in the function and then have no indentation in the rest of the function body

Avoid using both os and pathlib when pathlib alone should work

update type annotations in signature to be compatible for 3.8 with download_to_path, use union literal for autocomplete in example_name.


Discussion / hints:

Fix patch:
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 6ec5f0a..d539e96 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -9,7 +9,8 @@
 		"ghcr.io/dhoeric/features/act:1": {},
 		"ghcr.io/hspaans/devcontainer-features/pytest:1": {},
 		"ghcr.io/meaningful-ooo/devcontainer-features/fish:1": {},
-		"ghcr.io/stuartleeks/dev-container-features/shell-history:0": {}
+		"ghcr.io/stuartleeks/dev-container-features/shell-history:0": {},
+		"ghcr.io/devcontainers/features/github-cli:1": {}
 	},
 
 	// Use 'forwardPorts' to make a list of ports inside the container available locally.
diff --git a/.vscode/settings.json b/.vscode/settings.json
index af7446d..c5709fe 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,10 @@
 {
     "python.analysis.extraPaths": [
         "./src"
-    ]
+    ],
+    "python.testing.pytestArgs": [
+        "tests"
+    ],
+    "python.testing.unittestEnabled": false,
+    "python.testing.pytestEnabled": true
 }
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 357670d..47af26f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -62,7 +62,6 @@ Pulling it all together, we run a projection and store it in a variable `model`
 ```python
 model = Annuity(data = policy_data,
                 basis = basis,
-                do_run = True,
                 proj_len = 40,
                 )
 ```
@@ -78,7 +77,7 @@ Output: `379.7484060121289`
 We can view results as a DataFrame:
 
 ```python
-model_cashflows = model.ToDataFrame()
+model_cashflows = model.df
 model_cashflows.head()
 ```
 
diff --git a/examples/notebook/using_dataclasses.ipynb b/examples/notebook/using_dataclasses.ipynb
new file mode 100644
index 0000000..dabfec1
--- /dev/null
+++ b/examples/notebook/using_dataclasses.ipynb
@@ -0,0 +1,730 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# Using Dataclasses\n",
+    "\n",
+    "Heavylight doesn't prescribe any specific input structures, so you can store data directly in attributes, or as classes, dictionaries (or even directly query databases for data).  Using `dataclass` allows for type checking and autocompletion."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 51,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from dataclasses import dataclass, fields\n",
+    "from heavylight import Model, Table\n",
+    "from typing import Literal"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 60,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Rather than dictionaries etc, we can also specify model data/assumptions etc as dataclasses.\n",
+    "# (caveat - dataclasses use dictionaries anyway.)\n",
+    "\n",
+    "@dataclass\n",
+    "class Data:\n",
+    "    init_age: int\n",
+    "    init_fund: float\n",
+    "    term_m: int\n",
+    "\n",
+    "@dataclass\n",
+    "class Basis:\n",
+    "    growth_rate_pm: float\n",
+    "    q_x: Table\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 40,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "class TestModel(Model):\n",
+    "    # specify the data and basis types so we can typecheck/autocomplete.\n",
+    "    data: Data\n",
+    "    basis: Basis\n",
+    "    product: Literal['ProdA' , 'ProdB']\n",
+    "    \n",
+    "    def t(self, t):\n",
+    "        return t\n",
+    "    \n",
+    "    def age(self, t):\n",
+    "        if t == 0:\n",
+    "            return self.data.init_age\n",
+    "        elif t % 12 == 0:\n",
+    "            return self.age(t - 1) + 1\n",
+    "        else:\n",
+    "            return self.age(t - 1)\n",
+    "    \n",
+    "    def fund_value(self, t):\n",
+    "        if t == 0:\n",
+    "            return self.data.init_fund\n",
+    "        else:\n",
+    "            return self.fund_value(t - 1) * (1 + self.basis.growth_rate_pm)\n",
+    "\n",
+    "    def product_ind(self, t):\n",
+    "        if self.product == 'ProdA':\n",
+    "            return 1\n",
+    "        elif self.product == 'ProdB':\n",
+    "            return 2\n",
+    "        else:\n",
+    "            return -1\n",
+    "\n",
+    "    def q_x_m(self, t):\n",
+    "        return self.basis.q_x[t] / 12\n",
+    "\n",
+    "    def months_to_maturity(self, t):\n",
+    "        return max(self.data.term_m - t, 0)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 41,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# note that positional assignment can be used, but care needs to be taken that inputs aren't transposed\n",
+    "data = Data(35, 1234.5, 14)\n",
+    "basis = Basis(growth_rate_pm = 0.04/12, q_x = Table.read_csv('sample_q_x_table.csv'))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 46,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "model = TestModel(data=data, basis=basis, proj_len=20, product='ProdA')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 45,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>t</th>\n",
+       "      <th>age</th>\n",
+       "      <th>fund_value</th>\n",
+       "      <th>months_to_maturity</th>\n",
+       "      <th>product_ind</th>\n",
+       "      <th>q_x_m</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>0</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1234.500000</td>\n",
+       "      <td>14</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.005510</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>1</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1238.615000</td>\n",
+       "      <td>13</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.005721</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>2</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1242.743717</td>\n",
+       "      <td>12</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.005941</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>3</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1246.886196</td>\n",
+       "      <td>11</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.006170</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>4</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1251.042483</td>\n",
+       "      <td>10</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.006407</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>5</th>\n",
+       "      <td>5</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1255.212625</td>\n",
+       "      <td>9</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.006654</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>6</th>\n",
+       "      <td>6</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1259.396667</td>\n",
+       "      <td>8</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.006911</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>7</th>\n",
+       "      <td>7</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1263.594656</td>\n",
+       "      <td>7</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.007178</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>8</th>\n",
+       "      <td>8</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1267.806638</td>\n",
+       "      <td>6</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.007456</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>9</th>\n",
+       "      <td>9</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1272.032660</td>\n",
+       "      <td>5</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.007745</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>10</th>\n",
+       "      <td>10</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1276.272769</td>\n",
+       "      <td>4</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.008045</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>11</th>\n",
+       "      <td>11</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1280.527011</td>\n",
+       "      <td>3</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.008357</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>12</th>\n",
+       "      <td>12</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1284.795435</td>\n",
+       "      <td>2</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.008681</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>13</th>\n",
+       "      <td>13</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1289.078086</td>\n",
+       "      <td>1</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.009018</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>14</th>\n",
+       "      <td>14</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1293.375013</td>\n",
+       "      <td>0</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.009368</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>15</th>\n",
+       "      <td>15</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1297.686263</td>\n",
+       "      <td>0</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.009733</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>16</th>\n",
+       "      <td>16</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1302.011884</td>\n",
+       "      <td>0</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.010112</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>17</th>\n",
+       "      <td>17</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1306.351924</td>\n",
+       "      <td>0</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.010505</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>18</th>\n",
+       "      <td>18</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1310.706430</td>\n",
+       "      <td>0</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.010915</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>19</th>\n",
+       "      <td>19</td>\n",
+       "      <td>36</td>\n",
+       "      <td>1315.075452</td>\n",
+       "      <td>0</td>\n",
+       "      <td>-1</td>\n",
+       "      <td>0.011340</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "     t  age   fund_value  months_to_maturity  product_ind     q_x_m\n",
+       "0    0   35  1234.500000                  14           -1  0.005510\n",
+       "1    1   35  1238.615000                  13           -1  0.005721\n",
+       "2    2   35  1242.743717                  12           -1  0.005941\n",
+       "3    3   35  1246.886196                  11           -1  0.006170\n",
+       "4    4   35  1251.042483                  10           -1  0.006407\n",
+       "5    5   35  1255.212625                   9           -1  0.006654\n",
+       "6    6   35  1259.396667                   8           -1  0.006911\n",
+       "7    7   35  1263.594656                   7           -1  0.007178\n",
+       "8    8   35  1267.806638                   6           -1  0.007456\n",
+       "9    9   35  1272.032660                   5           -1  0.007745\n",
+       "10  10   35  1276.272769                   4           -1  0.008045\n",
+       "11  11   35  1280.527011                   3           -1  0.008357\n",
+       "12  12   36  1284.795435                   2           -1  0.008681\n",
+       "13  13   36  1289.078086                   1           -1  0.009018\n",
+       "14  14   36  1293.375013                   0           -1  0.009368\n",
+       "15  15   36  1297.686263                   0           -1  0.009733\n",
+       "16  16   36  1302.011884                   0           -1  0.010112\n",
+       "17  17   36  1306.351924                   0           -1  0.010505\n",
+       "18  18   36  1310.706430                   0           -1  0.010915\n",
+       "19  19   36  1315.075452                   0           -1  0.011340"
+      ]
+     },
+     "execution_count": 45,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model.df"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "0.0033333333333333335"
+      ]
+     },
+     "execution_count": 34,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model.basis.growth_rate_pm"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 35,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "35"
+      ]
+     },
+     "execution_count": 35,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model.data.init_age"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 36,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>t</th>\n",
+       "      <th>q_x_m</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>0</td>\n",
+       "      <td>0.005510</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>1</td>\n",
+       "      <td>0.005721</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>2</td>\n",
+       "      <td>0.005941</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>3</td>\n",
+       "      <td>0.006170</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>4</td>\n",
+       "      <td>0.006407</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>5</th>\n",
+       "      <td>5</td>\n",
+       "      <td>0.006654</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>6</th>\n",
+       "      <td>6</td>\n",
+       "      <td>0.006911</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>7</th>\n",
+       "      <td>7</td>\n",
+       "      <td>0.007178</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>8</th>\n",
+       "      <td>8</td>\n",
+       "      <td>0.007456</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>9</th>\n",
+       "      <td>9</td>\n",
+       "      <td>0.007745</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>10</th>\n",
+       "      <td>10</td>\n",
+       "      <td>0.008045</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>11</th>\n",
+       "      <td>11</td>\n",
+       "      <td>0.008357</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>12</th>\n",
+       "      <td>12</td>\n",
+       "      <td>0.008681</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>13</th>\n",
+       "      <td>13</td>\n",
+       "      <td>0.009018</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>14</th>\n",
+       "      <td>14</td>\n",
+       "      <td>0.009368</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>15</th>\n",
+       "      <td>15</td>\n",
+       "      <td>0.009733</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>16</th>\n",
+       "      <td>16</td>\n",
+       "      <td>0.010112</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>17</th>\n",
+       "      <td>17</td>\n",
+       "      <td>0.010505</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>18</th>\n",
+       "      <td>18</td>\n",
+       "      <td>0.010915</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>19</th>\n",
+       "      <td>19</td>\n",
+       "      <td>0.011340</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "     t     q_x_m\n",
+       "0    0  0.005510\n",
+       "1    1  0.005721\n",
+       "2    2  0.005941\n",
+       "3    3  0.006170\n",
+       "4    4  0.006407\n",
+       "5    5  0.006654\n",
+       "6    6  0.006911\n",
+       "7    7  0.007178\n",
+       "8    8  0.007456\n",
+       "9    9  0.007745\n",
+       "10  10  0.008045\n",
+       "11  11  0.008357\n",
+       "12  12  0.008681\n",
+       "13  13  0.009018\n",
+       "14  14  0.009368\n",
+       "15  15  0.009733\n",
+       "16  16  0.010112\n",
+       "17  17  0.010505\n",
+       "18  18  0.010915\n",
+       "19  19  0.011340"
+      ]
+     },
+     "execution_count": 36,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model.q_x_m.df"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 65,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>t</th>\n",
+       "      <th>age</th>\n",
+       "      <th>fund_value</th>\n",
+       "      <th>months_to_maturity</th>\n",
+       "      <th>product_ind</th>\n",
+       "      <th>q_x_m</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>0</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1234.500000</td>\n",
+       "      <td>14</td>\n",
+       "      <td>2</td>\n",
+       "      <td>0.005510</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>1</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1238.615000</td>\n",
+       "      <td>13</td>\n",
+       "      <td>2</td>\n",
+       "      <td>0.005721</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>2</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1242.743717</td>\n",
+       "      <td>12</td>\n",
+       "      <td>2</td>\n",
+       "      <td>0.005941</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>3</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1246.886196</td>\n",
+       "      <td>11</td>\n",
+       "      <td>2</td>\n",
+       "      <td>0.006170</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>4</td>\n",
+       "      <td>35</td>\n",
+       "      <td>1251.042483</td>\n",
+       "      <td>10</td>\n",
+       "      <td>2</td>\n",
+       "      <td>0.006407</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "   t  age   fund_value  months_to_maturity  product_ind     q_x_m\n",
+       "0  0   35  1234.500000                  14            2  0.005510\n",
+       "1  1   35  1238.615000                  13            2  0.005721\n",
+       "2  2   35  1242.743717                  12            2  0.005941\n",
+       "3  3   35  1246.886196                  11            2  0.006170\n",
+       "4  4   35  1251.042483                  10            2  0.006407"
+      ]
+     },
+     "execution_count": 65,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "TestModel(proj_len=5, data=data, basis=basis, product='ProdB').df"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "(Field(name='init_age',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x1017db650>,default_factory=<dataclasses._MISSING_TYPE object at 0x1017db650>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD),\n",
+       " Field(name='init_fund',type=<class 'float'>,default=<dataclasses._MISSING_TYPE object at 0x1017db650>,default_factory=<dataclasses._MISSING_TYPE object at 0x1017db650>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD),\n",
+       " Field(name='term_m',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x1017db650>,default_factory=<dataclasses._MISSING_TYPE object at 0x1017db650>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD))"
+      ]
+     },
+     "execution_count": 54,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "fields(data)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# walking from one basis to another"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "heavylight",
+   "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.11.5"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/pyproject.toml b/pyproject.toml
index 9e22587..69271cf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,6 +10,7 @@ name = "heavylight"
 version = "1.0.5"
 authors = [
   { name="Lewis Fogden", email="lewisfogden@gmail.com" },
+  { name="Matthew Caseres", email="ADD@EMAIL.HERE"}
 ]
 description = "Heavylight Actuarial Modelling Framework"
 readme = "README.md"
diff --git a/src/heavylight/__init__.py b/src/heavylight/__init__.py
index a8bd946..851e614 100644
--- a/src/heavylight/__init__.py
+++ b/src/heavylight/__init__.py
@@ -2,5 +2,6 @@
 from .heavytables import Table
 from .memory_optimized_model import LightModel
 from .memory_optimized_cache import CacheGraph
+from .make_examples import make_example
 
-__version__ = '1.0.5'
+__version__ = '1.0.6'
diff --git a/src/heavylight/examples/protection/protection_model_base.py b/src/heavylight/examples/protection/protection_model_base.py
new file mode 100644
index 0000000..d74e4fd
--- /dev/null
+++ b/src/heavylight/examples/protection/protection_model_base.py
@@ -0,0 +1,97 @@
+import heavylight
+
+class TermAssurance(heavylight.Model):
+    def t(self, t):
+        return t
+
+    def net_cf(self, t):
+        return self.premiums(t) - self.claims(t) - self.expenses(t)
+    
+    def premium_pp(self, t):
+        """monthly premium"""
+        return self.data["annual_premium"] / 12
+    
+    def claim_pp(self, t):
+        if t == 0:
+            return self.data["sum_assured"]
+        elif t > self.data["term_y"] * 12:
+            return 0
+        elif self.data["shape"] == "level":
+            return self.data["sum_assured"]
+        elif self.data["shape"] == "decreasing":
+            r = (1 + 0.07)**(1/12)-1
+            S = self.data["sum_assured"]
+            T = self.data["term_y"] * 12
+            outstanding = S * ((1 + r)**T - (1 + r)**t)/((1 + r)**T - 1)
+            return outstanding
+        else:
+            raise ValueError("Parameter 'shape' must be 'level' or 'decreasing'")
+    
+    def inflation_factor(self, t):
+        """annual"""
+        return (1 + self.basis["cost_inflation_pa"])**(t//12)
+
+    def v(self, t):
+        """present value of 1 discounted from time t to time 0"""
+        if t == 0:
+            return 1
+        else:
+            return self.v(t - 1) / (1 +self.basis["forward_rates"][t])
+
+    def premiums(self, t):
+        return self.premium_pp(t) * self.num_pols_if(t)
+    
+    def pv_premiums(self, t):
+        return self.v(t) * self.premiums(t)
+    
+    def duration(self, t):
+        """duration in force in years"""
+        return t//12
+    
+    def claims(self, t):
+        return self.claim_pp(t) * self.num_deaths(t)
+    
+    def pv_claims(self, t):
+        return self.v(t) * self.claims(t)
+      
+    def expenses(self, t):
+        if t == 0:
+            return self.basis["initial_expense"]
+        else:
+            return self.num_pols_if(t) * self.basis["expense_pp"]/12 * self.inflation_factor(t)
+      
+    def pv_expenses(self, t):
+        return self.v(t) * self.expenses(t)
+
+    def num_pols_if(self, t):
+        """number of policies in force"""
+        if t == 0:
+            return self.data["init_pols_if"]
+        elif t > self.data["term_y"] * 12:
+            return 0
+        else:
+            return self.num_pols_if(t-1) - self.num_exits(t-1) - self.num_deaths(t-1)
+    
+    def num_exits(self, t):
+        """exits occurring at time t"""
+        return self.num_pols_if(t) * (1-(1 - self.basis["lapse_rate_pa"])**(1/12))
+      
+    def num_deaths(self, t):
+        """deaths occurring at time t"""
+        return self.num_pols_if(t) * self.q_x_12(t)
+    
+    def age(self, t):
+        return self.data["age_at_entry"] + t//12
+    
+    
+    def q_x_12(self, t):
+        return 1-(1- self.q_x_rated(t))**(1/12)
+    
+    def q_x(self, t):
+        return self.basis["mort_table"][self.age(t), self.duration(t), self.data["smoker_status"]]
+            
+    def q_x_rated(self, t):
+        return max(0, min(1 , self.q_x(t) * (1 + self.data["extra_mortality"]) ) )
+        
+    def commission(self, t):
+        return 0
diff --git a/src/heavylight/examples/protection/protection_model_np.py b/src/heavylight/examples/protection/protection_model_np.py
new file mode 100644
index 0000000..52f23ed
--- /dev/null
+++ b/src/heavylight/examples/protection/protection_model_np.py
@@ -0,0 +1,106 @@
+import heavylight
+import numpy as np
+
+class TermAssurance(heavylight.Model):
+    def t(self, t):
+        return t
+
+    def net_cf(self, t):
+        return self.premiums(t) - self.claims(t) - self.expenses(t)
+    
+    def premium_pp(self, t):
+        """monthly premium"""
+        return self.data["annual_premium"] / 12
+    
+    def sum_assured(self, t):
+        return np.where(
+            self.data["shape"] == 'level',
+            self.data["sum_assured"],
+            self.decreasing_sa(t)
+        )
+
+    def decreasing_sa(self, t):
+        """sum assured if decreasing, tracking mortgage - interest rate of 7% assumed (could be parameterised)"""
+        r = (1 + 0.07)**(1/12) - 1
+        S = self.data["sum_assured"]
+        T = self.data["term_y"] * 12
+        outstanding = S * ((1 + r)**T - (1 + r)**t)/((1 + r)**T - 1)
+        return outstanding
+
+    def claim_pp(self, t):
+        return np.where(
+            t > self.data["term_y"] * 12,
+            0,
+            self.sum_assured(t)
+        )
+
+    def inflation_factor(self, t):
+        """annual"""
+        return (1 + self.basis["cost_inflation_pa"])**(t // 12)
+
+    def v(self, t):
+        """present value of 1 discounted from time t to time 0"""
+        if t == 0:
+            return 1
+        else:
+            return self.v(t - 1) / (1 + self.basis["forward_rates"][t])
+
+    def premiums(self, t):
+        return self.premium_pp(t) * self.num_pols_if(t)
+    
+    def pv_premiums(self, t):
+        return self.v(t) * self.premiums(t)
+    
+    def duration(self, t):
+        """duration in force in years"""
+        return t // 12
+    
+    def claims(self, t):
+        return self.claim_pp(t) * self.num_deaths(t)
+    
+    def pv_claims(self, t):
+        return self.v(t) * self.claims(t)
+      
+    def expenses(self, t):
+        if t == 0:
+            return self.basis["initial_expense"]
+        else:
+            return self.num_pols_if(t) * self.basis["expense_pp"] / 12 * self.inflation_factor(t)
+      
+    def pv_expenses(self, t):
+        return self.v(t) * self.expenses(t)
+
+    def num_pols_if(self, t):
+        """number of policies in force"""
+        if t == 0:
+            return self.data["init_pols_if"]    
+        # if statements need to be vectorised, using np.where (same logic as excel IF())
+        else:
+            return np.where(
+                t > self.data["term_y"] * 12,
+                0,
+                self.num_pols_if(t - 1) - self.num_exits(t - 1) - self.num_deaths(t - 1)
+            )
+    
+    def num_exits(self, t):
+        """exits occurring at time t"""
+        return self.num_pols_if(t) * (1 - (1 - self.basis["lapse_rate_pa"])**(1/12))
+      
+    def num_deaths(self, t):
+        """deaths occurring at time t"""
+        return self.num_pols_if(t) * self.q_x_12(t)
+    
+    def age(self, t):
+        return self.data["age_at_entry"] + t // 12
+    
+    def q_x_12(self, t):
+        return 1 - (1 - self.q_x_rated(t))**(1/12)
+    
+    def q_x(self, t):
+        return self.basis["mort_table"][self.age(t), self.duration(t), self.data["smoker_status"]]
+            
+    def q_x_rated(self, t):
+        return np.clip(self.q_x(t) * (1 + self.data["extra_mortality"]), 0, 1)
+        
+    def commission(self, t):
+        return 0
diff --git a/src/heavylight/examples/protection/protection_risk_model_generic.xlsx b/src/heavylight/examples/protection/protection_risk_model_generic.xlsx
new file mode 100644
index 0000000..7a95e77
Binary files /dev/null and b/src/heavylight/examples/protection/protection_risk_model_generic.xlsx differ
diff --git a/src/heavylight/examples/protection/readme.md b/src/heavylight/examples/protection/readme.md
new file mode 100644
index 0000000..f9bd4bf
--- /dev/null
+++ b/src/heavylight/examples/protection/readme.md
@@ -0,0 +1,3 @@
+# Protection Model
+
+This example contains a protection model, coded both non-vectorised and vectorised.
\ No newline at end of file
diff --git a/src/heavylight/examples/protection/run_model_base.py b/src/heavylight/examples/protection/run_model_base.py
new file mode 100644
index 0000000..ef09364
--- /dev/null
+++ b/src/heavylight/examples/protection/run_model_base.py
@@ -0,0 +1,66 @@
+# Example Protection Model
+#
+#
+
+# %%
+import heavylight
+from heavylight import Table
+from protection_model_base import TermAssurance
+
+def solve_prot_premium(model: heavylight.Model, data: dict, basis: dict):
+    
+    data = data.copy()
+
+    pricing_entries = {
+            "annual_premium":1,
+            "init_pols_if":1,
+            }
+    
+    data.update(pricing_entries)
+
+    proj_len = data["term_y"] * 12 + 1
+
+    model_inst = model(data=data, basis=basis, proj_len=proj_len)
+            
+    # extract npvs
+    npv_claims = model_inst.pv_claims.sum()
+    npv_expenses = model_inst.pv_expenses.sum()
+    npv_premiums = model_inst.pv_premiums.sum()
+            
+    # calculate premium
+    
+    annual_risk_premium = (npv_claims + npv_expenses) / npv_premiums
+    monthly_risk_premium = annual_risk_premium / 12
+    return round(monthly_risk_premium, 2)
+
+# %%
+if __name__=='__main__':
+    
+    basis = {
+        "cost_inflation_pa": 0.02,
+        "initial_expense": 500,
+        "expense_pp": 10,
+        "lapse_rate_pa": 0.1,
+        "mort_table": Table.read_csv(r"tables/q_x_generic.csv"),
+        "forward_rates": Table.read_csv(r"tables/forward_rates.csv"),
+    }
+
+    data = {
+        "sum_assured": 100_000,
+        "age_at_entry": 49,
+        "term_y": 35,
+        "smoker_status": "N",
+        "shape": "level",
+        "annual_premium": 1,
+        "init_pols_if": 1,
+        "extra_mortality": 0,
+        "sex": "F"
+    }
+
+    model = TermAssurance(basis=basis, data=data, proj_len=data["term_y"] * 12 + 12)
+  
+    monthly_premium = solve_prot_premium(TermAssurance, data, basis)
+    print("Premium: ", monthly_premium)
+   
+
+    
diff --git a/src/heavylight/examples/protection/run_model_np.py b/src/heavylight/examples/protection/run_model_np.py
new file mode 100644
index 0000000..21feb30
--- /dev/null
+++ b/src/heavylight/examples/protection/run_model_np.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+# required libraries:
+# heavylight
+# pip install heavylight
+
+# %%
+import heavylight
+from heavylight import Table
+from protection_model_np import TermAssurance
+import numpy as np
+import pandas as pd
+
+def solve_prot_premium(model: heavylight.Model, data: dict, basis: dict):
+    
+    data = data.copy()
+
+    pricing_entries = {
+            "annual_premium":1,
+            "init_pols_if":1,
+            }
+    
+    data.update(pricing_entries)
+
+    proj_len = int(max(data["term_y"]) * 12 + 1)
+    model_inst = model(data=data, basis=basis, proj_len=proj_len)
+            
+    # extract npvs
+    npv_claims = model_inst.pv_claims.sum()
+    npv_expenses = model_inst.pv_expenses.sum()
+    npv_premiums = model_inst.pv_premiums.sum()
+            
+    # calculate premium
+    
+    annual_risk_premium = (npv_claims + npv_expenses) / npv_premiums
+    monthly_risk_premium = annual_risk_premium / 12
+    return np.round(monthly_risk_premium, 2)
+
+# %%
+if __name__=='__main__':
+    
+    basis = {
+        "cost_inflation_pa": 0.02,
+        "initial_expense": 500,
+        "expense_pp": 10,
+        "lapse_rate_pa": 0.1,
+        "mort_table": Table.read_csv(r"tables/q_x_generic.csv"),
+        "forward_rates": Table.read_csv(r"tables/forward_rates.csv"),
+    }
+
+    data = {
+        "sum_assured": 100_000,
+        "age_at_entry": 49,
+        "term_y": 35,
+        "smoker_status": "S",
+        "shape": "level",
+        "annual_premium": 1,
+        "init_pols_if": 1,
+        "extra_mortality": 0,
+        "sex": "F"
+    }
+    data = {k:np.array([v] * 3) for k, v in data.items()}
+
+    rng = np.random.default_rng(seed=42)
+
+    # override the single datapoint `pols`
+    pols = 10_000
+    data = dict(
+        sum_assured = rng.integers(10_000, 250_000, pols),
+        age_at_entry = rng.integers(20, 50, pols),
+        term_y = rng.integers(10, 30, pols),
+        smoker_status = rng.choice(['S', 'N'], pols),
+        shape = rng.choice(['level', 'decreasing'], pols),
+        annual_premium = np.ones(pols),
+        init_pols_if = np.ones(pols),
+        extra_mortality = np.zeros(pols),
+        sex = rng.choice(['F', 'M'], pols),
+    )
+    
+    monthly_premium = solve_prot_premium(TermAssurance, data, basis)
+    print("Premium: ", monthly_premium)
+
+    do_output = input('Save output as result excel file? (Y/n):')
+    if do_output[0].lower() == 'y':
+        df = pd.DataFrame(data)
+        df['monthly_premium'] = monthly_premium
+        df.to_excel('output/projection_result.xlsx', index=False)
+        print('Saved to output/projection_result.xlsx')
+# %%
+    
diff --git a/src/heavylight/examples/protection/tables/forward_rates.csv b/src/heavylight/examples/protection/tables/forward_rates.csv
new file mode 100644
index 0000000..a401d24
--- /dev/null
+++ b/src/heavylight/examples/protection/tables/forward_rates.csv
@@ -0,0 +1,763 @@
+month|int,forward_rate|float
+0,0
+1,0.0004625
+2,0.0004625
+3,0.0004625
+4,0.0004625
+5,0.0004625
+6,0.0004625
+7,0.0004625
+8,0.0004625
+9,0.0004625
+10,0.0004625
+11,0.0004625
+12,0.0004625
+13,0.000677638
+14,0.000677638
+15,0.000677638
+16,0.000677638
+17,0.000677638
+18,0.000677638
+19,0.000677638
+20,0.000677638
+21,0.000677638
+22,0.000677638
+23,0.000677638
+24,0.000677638
+25,0.000830269
+26,0.000830269
+27,0.000830269
+28,0.000830269
+29,0.000830269
+30,0.000830269
+31,0.000830269
+32,0.000830269
+33,0.000830269
+34,0.000830269
+35,0.000830269
+36,0.000830269
+37,0.000916969
+38,0.000916969
+39,0.000916969
+40,0.000916969
+41,0.000916969
+42,0.000916969
+43,0.000916969
+44,0.000916969
+45,0.000916969
+46,0.000916969
+47,0.000916969
+48,0.000916969
+49,0.001017917
+50,0.001017917
+51,0.001017917
+52,0.001017917
+53,0.001017917
+54,0.001017917
+55,0.001017917
+56,0.001017917
+57,0.001017917
+58,0.001017917
+59,0.001017917
+60,0.001017917
+61,0.00108128
+62,0.00108128
+63,0.00108128
+64,0.00108128
+65,0.00108128
+66,0.00108128
+67,0.00108128
+68,0.00108128
+69,0.00108128
+70,0.00108128
+71,0.00108128
+72,0.00108128
+73,0.001140487
+74,0.001140487
+75,0.001140487
+76,0.001140487
+77,0.001140487
+78,0.001140487
+79,0.001140487
+80,0.001140487
+81,0.001140487
+82,0.001140487
+83,0.001140487
+84,0.001140487
+85,0.001195533
+86,0.001195533
+87,0.001195533
+88,0.001195533
+89,0.001195533
+90,0.001195533
+91,0.001195533
+92,0.001195533
+93,0.001195533
+94,0.001195533
+95,0.001195533
+96,0.001195533
+97,0.001260629
+98,0.001260629
+99,0.001260629
+100,0.001260629
+101,0.001260629
+102,0.001260629
+103,0.001260629
+104,0.001260629
+105,0.001260629
+106,0.001260629
+107,0.001260629
+108,0.001260629
+109,0.001320719
+110,0.001320719
+111,0.001320719
+112,0.001320719
+113,0.001320719
+114,0.001320719
+115,0.001320719
+116,0.001320719
+117,0.001320719
+118,0.001320719
+119,0.001320719
+120,0.001320719
+121,0.001338988
+122,0.001338988
+123,0.001338988
+124,0.001338988
+125,0.001338988
+126,0.001338988
+127,0.001338988
+128,0.001338988
+129,0.001338988
+130,0.001338988
+131,0.001338988
+132,0.001338988
+133,0.001352259
+134,0.001352259
+135,0.001352259
+136,0.001352259
+137,0.001352259
+138,0.001352259
+139,0.001352259
+140,0.001352259
+141,0.001352259
+142,0.001352259
+143,0.001352259
+144,0.001352259
+145,0.001331268
+146,0.001331268
+147,0.001331268
+148,0.001331268
+149,0.001331268
+150,0.001331268
+151,0.001331268
+152,0.001331268
+153,0.001331268
+154,0.001331268
+155,0.001331268
+156,0.001331268
+157,0.001339563
+158,0.001339563
+159,0.001339563
+160,0.001339563
+161,0.001339563
+162,0.001339563
+163,0.001339563
+164,0.001339563
+165,0.001339563
+166,0.001339563
+167,0.001339563
+168,0.001339563
+169,0.001365418
+170,0.001365418
+171,0.001365418
+172,0.001365418
+173,0.001365418
+174,0.001365418
+175,0.001365418
+176,0.001365418
+177,0.001365418
+178,0.001365418
+179,0.001365418
+180,0.001365418
+181,0.001308556
+182,0.001308556
+183,0.001308556
+184,0.001308556
+185,0.001308556
+186,0.001308556
+187,0.001308556
+188,0.001308556
+189,0.001308556
+190,0.001308556
+191,0.001308556
+192,0.001308556
+193,0.001305189
+194,0.001305189
+195,0.001305189
+196,0.001305189
+197,0.001305189
+198,0.001305189
+199,0.001305189
+200,0.001305189
+201,0.001305189
+202,0.001305189
+203,0.001305189
+204,0.001305189
+205,0.001281793
+206,0.001281793
+207,0.001281793
+208,0.001281793
+209,0.001281793
+210,0.001281793
+211,0.001281793
+212,0.001281793
+213,0.001281793
+214,0.001281793
+215,0.001281793
+216,0.001281793
+217,0.001250902
+218,0.001250902
+219,0.001250902
+220,0.001250902
+221,0.001250902
+222,0.001250902
+223,0.001250902
+224,0.001250902
+225,0.001250902
+226,0.001250902
+227,0.001250902
+228,0.001250902
+229,0.001195847
+230,0.001195847
+231,0.001195847
+232,0.001195847
+233,0.001195847
+234,0.001195847
+235,0.001195847
+236,0.001195847
+237,0.001195847
+238,0.001195847
+239,0.001195847
+240,0.001195847
+241,0.001165835
+242,0.001165835
+243,0.001165835
+244,0.001165835
+245,0.001165835
+246,0.001165835
+247,0.001165835
+248,0.001165835
+249,0.001165835
+250,0.001165835
+251,0.001165835
+252,0.001165835
+253,0.001094184
+254,0.001094184
+255,0.001094184
+256,0.001094184
+257,0.001094184
+258,0.001094184
+259,0.001094184
+260,0.001094184
+261,0.001094184
+262,0.001094184
+263,0.001094184
+264,0.001094184
+265,0.001089185
+266,0.001089185
+267,0.001089185
+268,0.001089185
+269,0.001089185
+270,0.001089185
+271,0.001089185
+272,0.001089185
+273,0.001089185
+274,0.001089185
+275,0.001089185
+276,0.001089185
+277,0.001064203
+278,0.001064203
+279,0.001064203
+280,0.001064203
+281,0.001064203
+282,0.001064203
+283,0.001064203
+284,0.001064203
+285,0.001064203
+286,0.001064203
+287,0.001064203
+288,0.001064203
+289,0.001057539
+290,0.001057539
+291,0.001057539
+292,0.001057539
+293,0.001057539
+294,0.001057539
+295,0.001057539
+296,0.001057539
+297,0.001057539
+298,0.001057539
+299,0.001057539
+300,0.001057539
+301,0.001050876
+302,0.001050876
+303,0.001050876
+304,0.001050876
+305,0.001050876
+306,0.001050876
+307,0.001050876
+308,0.001050876
+309,0.001050876
+310,0.001050876
+311,0.001050876
+312,0.001050876
+313,0.001021739
+314,0.001021739
+315,0.001021739
+316,0.001021739
+317,0.001021739
+318,0.001021739
+319,0.001021739
+320,0.001021739
+321,0.001021739
+322,0.001021739
+323,0.001021739
+324,0.001021739
+325,0.001013411
+326,0.001013411
+327,0.001013411
+328,0.001013411
+329,0.001013411
+330,0.001013411
+331,0.001013411
+332,0.001013411
+333,0.001013411
+334,0.001013411
+335,0.001013411
+336,0.001013411
+337,0.001005083
+338,0.001005083
+339,0.001005083
+340,0.001005083
+341,0.001005083
+342,0.001005083
+343,0.001005083
+344,0.001005083
+345,0.001005083
+346,0.001005083
+347,0.001005083
+348,0.001005083
+349,0.000971795
+350,0.000971795
+351,0.000971795
+352,0.000971795
+353,0.000971795
+354,0.000971795
+355,0.000971795
+356,0.000971795
+357,0.000971795
+358,0.000971795
+359,0.000971795
+360,0.000971795
+361,0.000936021
+362,0.000936021
+363,0.000936021
+364,0.000936021
+365,0.000936021
+366,0.000936021
+367,0.000936021
+368,0.000936021
+369,0.000936021
+370,0.000936021
+371,0.000936021
+372,0.000936021
+373,0.000897761
+374,0.000897761
+375,0.000897761
+376,0.000897761
+377,0.000897761
+378,0.000897761
+379,0.000897761
+380,0.000897761
+381,0.000897761
+382,0.000897761
+383,0.000897761
+384,0.000897761
+385,0.000857018
+386,0.000857018
+387,0.000857018
+388,0.000857018
+389,0.000857018
+390,0.000857018
+391,0.000857018
+392,0.000857018
+393,0.000857018
+394,0.000857018
+395,0.000857018
+396,0.000857018
+397,0.000813794
+398,0.000813794
+399,0.000813794
+400,0.000813794
+401,0.000813794
+402,0.000813794
+403,0.000813794
+404,0.000813794
+405,0.000813794
+406,0.000813794
+407,0.000813794
+408,0.000813794
+409,0.000768092
+410,0.000768092
+411,0.000768092
+412,0.000768092
+413,0.000768092
+414,0.000768092
+415,0.000768092
+416,0.000768092
+417,0.000768092
+418,0.000768092
+419,0.000768092
+420,0.000768092
+421,0.000719912
+422,0.000719912
+423,0.000719912
+424,0.000719912
+425,0.000719912
+426,0.000719912
+427,0.000719912
+428,0.000719912
+429,0.000719912
+430,0.000719912
+431,0.000719912
+432,0.000719912
+433,0.000699955
+434,0.000699955
+435,0.000699955
+436,0.000699955
+437,0.000699955
+438,0.000699955
+439,0.000699955
+440,0.000699955
+441,0.000699955
+442,0.000699955
+443,0.000699955
+444,0.000699955
+445,0.000711532
+446,0.000711532
+447,0.000711532
+448,0.000711532
+449,0.000711532
+450,0.000711532
+451,0.000711532
+452,0.000711532
+453,0.000711532
+454,0.000711532
+455,0.000711532
+456,0.000711532
+457,0.000725609
+458,0.000725609
+459,0.000725609
+460,0.000725609
+461,0.000725609
+462,0.000725609
+463,0.000725609
+464,0.000725609
+465,0.000725609
+466,0.000725609
+467,0.000725609
+468,0.000725609
+469,0.000742186
+470,0.000742186
+471,0.000742186
+472,0.000742186
+473,0.000742186
+474,0.000742186
+475,0.000742186
+476,0.000742186
+477,0.000742186
+478,0.000742186
+479,0.000742186
+480,0.000742186
+481,0.000829409
+482,0.000829409
+483,0.000829409
+484,0.000829409
+485,0.000829409
+486,0.000829409
+487,0.000829409
+488,0.000829409
+489,0.000829409
+490,0.000829409
+491,0.000829409
+492,0.000829409
+493,0.000819422
+494,0.000819422
+495,0.000819422
+496,0.000819422
+497,0.000819422
+498,0.000819422
+499,0.000819422
+500,0.000819422
+501,0.000819422
+502,0.000819422
+503,0.000819422
+504,0.000819422
+505,0.000773697
+506,0.000773697
+507,0.000773697
+508,0.000773697
+509,0.000773697
+510,0.000773697
+511,0.000773697
+512,0.000773697
+513,0.000773697
+514,0.000773697
+515,0.000773697
+516,0.000773697
+517,0.000725498
+518,0.000725498
+519,0.000725498
+520,0.000725498
+521,0.000725498
+522,0.000725498
+523,0.000725498
+524,0.000725498
+525,0.000725498
+526,0.000725498
+527,0.000725498
+528,0.000725498
+529,0.000600151
+530,0.000600151
+531,0.000600151
+532,0.000600151
+533,0.000600151
+534,0.000600151
+535,0.000600151
+536,0.000600151
+537,0.000600151
+538,0.000600151
+539,0.000600151
+540,0.000600151
+541,0.000505604
+542,0.000505604
+543,0.000505604
+544,0.000505604
+545,0.000505604
+546,0.000505604
+547,0.000505604
+548,0.000505604
+549,0.000505604
+550,0.000505604
+551,0.000505604
+552,0.000505604
+553,0.000522946
+554,0.000522946
+555,0.000522946
+556,0.000522946
+557,0.000522946
+558,0.000522946
+559,0.000522946
+560,0.000522946
+561,0.000522946
+562,0.000522946
+563,0.000522946
+564,0.000522946
+565,0.000582594
+566,0.000582594
+567,0.000582594
+568,0.000582594
+569,0.000582594
+570,0.000582594
+571,0.000582594
+572,0.000582594
+573,0.000582594
+574,0.000582594
+575,0.000582594
+576,0.000582594
+577,0.000810155
+578,0.000810155
+579,0.000810155
+580,0.000810155
+581,0.000810155
+582,0.000810155
+583,0.000810155
+584,0.000810155
+585,0.000810155
+586,0.000810155
+587,0.000810155
+588,0.000810155
+589,0.001053374
+590,0.001053374
+591,0.001053374
+592,0.001053374
+593,0.001053374
+594,0.001053374
+595,0.001053374
+596,0.001053374
+597,0.001053374
+598,0.001053374
+599,0.001053374
+600,0.001053374
+601,0.00144044
+602,0.00144044
+603,0.00144044
+604,0.00144044
+605,0.00144044
+606,0.00144044
+607,0.00144044
+608,0.00144044
+609,0.00144044
+610,0.00144044
+611,0.00144044
+612,0.00144044
+613,0.00167697
+614,0.00167697
+615,0.00167697
+616,0.00167697
+617,0.00167697
+618,0.00167697
+619,0.00167697
+620,0.00167697
+621,0.00167697
+622,0.00167697
+623,0.00167697
+624,0.00167697
+625,0.001971346
+626,0.001971346
+627,0.001971346
+628,0.001971346
+629,0.001971346
+630,0.001971346
+631,0.001971346
+632,0.001971346
+633,0.001971346
+634,0.001971346
+635,0.001971346
+636,0.001971346
+637,0.0021905
+638,0.0021905
+639,0.0021905
+640,0.0021905
+641,0.0021905
+642,0.0021905
+643,0.0021905
+644,0.0021905
+645,0.0021905
+646,0.0021905
+647,0.0021905
+648,0.0021905
+649,0.002373665
+650,0.002373665
+651,0.002373665
+652,0.002373665
+653,0.002373665
+654,0.002373665
+655,0.002373665
+656,0.002373665
+657,0.002373665
+658,0.002373665
+659,0.002373665
+660,0.002373665
+661,0.002517246
+662,0.002517246
+663,0.002517246
+664,0.002517246
+665,0.002517246
+666,0.002517246
+667,0.002517246
+668,0.002517246
+669,0.002517246
+670,0.002517246
+671,0.002517246
+672,0.002517246
+673,0.002617689
+674,0.002617689
+675,0.002617689
+676,0.002617689
+677,0.002617689
+678,0.002617689
+679,0.002617689
+680,0.002617689
+681,0.002617689
+682,0.002617689
+683,0.002617689
+684,0.002617689
+685,0.00276998
+686,0.00276998
+687,0.00276998
+688,0.00276998
+689,0.00276998
+690,0.00276998
+691,0.00276998
+692,0.00276998
+693,0.00276998
+694,0.00276998
+695,0.00276998
+696,0.00276998
+697,0.002877345
+698,0.002877345
+699,0.002877345
+700,0.002877345
+701,0.002877345
+702,0.002877345
+703,0.002877345
+704,0.002877345
+705,0.002877345
+706,0.002877345
+707,0.002877345
+708,0.002877345
+709,0.002936272
+710,0.002936272
+711,0.002936272
+712,0.002936272
+713,0.002936272
+714,0.002936272
+715,0.002936272
+716,0.002936272
+717,0.002936272
+718,0.002936272
+719,0.002936272
+720,0.002936272
+721,0.003047121
+722,0.003047121
+723,0.003047121
+724,0.003047121
+725,0.003047121
+726,0.003047121
+727,0.003047121
+728,0.003047121
+729,0.003047121
+730,0.003047121
+731,0.003047121
+732,0.003047121
+733,0.003054989
+734,0.003054989
+735,0.003054989
+736,0.003054989
+737,0.003054989
+738,0.003054989
+739,0.003054989
+740,0.003054989
+741,0.003054989
+742,0.003054989
+743,0.003054989
+744,0.003054989
+745,0.003113946
+746,0.003113946
+747,0.003113946
+748,0.003113946
+749,0.003113946
+750,0.003113946
+751,0.003397032
+752,0.003736735
+753,0.004151928
+754,0.004670919
+755,0.005338193
+756,0.006227892
+757,0.006227892
+758,0.006227892
+759,0.006227892
+760,0.006227892
+761,0.006227892
diff --git a/src/heavylight/examples/protection/tables/q_x_generic.csv b/src/heavylight/examples/protection/tables/q_x_generic.csv
new file mode 100644
index 0000000..2803be8
--- /dev/null
+++ b/src/heavylight/examples/protection/tables/q_x_generic.csv
@@ -0,0 +1,877 @@
+age|int_bound,duration|int_bound,smoker_status|str,q_x|float
+18,0,N,0.000231067
+19,0,N,0.00023532
+20,0,N,0.000239936
+21,0,N,0.000244937
+22,0,N,0.000250346
+23,0,N,0.000256188
+24,0,N,0.00026249
+25,0,N,0.000269283
+26,0,N,0.000276599
+27,0,N,0.000284473
+28,0,N,0.000292946
+29,0,N,0.000302058
+30,0,N,0.000311857
+31,0,N,0.000322395
+32,0,N,0.000333725
+33,0,N,0.00034591
+34,0,N,0.000359016
+35,0,N,0.000373118
+36,0,N,0.000388294
+37,0,N,0.000404634
+38,0,N,0.000422236
+39,0,N,0.000441205
+40,0,N,0.00046166
+41,0,N,0.000483731
+42,0,N,0.00050756
+43,0,N,0.000533306
+44,0,N,0.000561143
+45,0,N,0.000591264
+46,0,N,0.000623883
+47,0,N,0.000659237
+48,0,N,0.000697588
+49,0,N,0.000739227
+50,0,N,0.000784479
+51,0,N,0.000833703
+52,0,N,0.000887301
+53,0,N,0.000945718
+54,0,N,0.001009455
+55,0,N,0.001079068
+56,0,N,0.001155179
+57,0,N,0.001238486
+58,0,N,0.001329769
+59,0,N,0.001429905
+60,0,N,0.001539876
+61,0,N,0.001660789
+62,0,N,0.001793889
+63,0,N,0.001940577
+64,0,N,0.002102435
+65,0,N,0.00228125
+66,0,N,0.002479041
+67,0,N,0.002698095
+68,0,N,0.002941001
+69,0,N,0.0032107
+70,0,N,0.00351053
+71,0,N,0.00384429
+72,0,N,0.004216304
+73,0,N,0.004631502
+74,0,N,0.005095513
+75,0,N,0.005614769
+76,0,N,0.00619663
+77,0,N,0.006849531
+78,0,N,0.007583145
+79,0,N,0.008408587
+80,0,N,0.009338637
+81,0,N,0.010388014
+82,0,N,0.01157369
+83,0,N,0.012915259
+84,0,N,0.014435375
+85,0,N,0.01616026
+86,0,N,0.018120308
+87,0,N,0.020350796
+88,0,N,0.022892723
+89,0,N,0.025793799
+90,0,N,0.029109623
+18,1,N,0.000254174
+19,1,N,0.000258852
+20,1,N,0.00026393
+21,1,N,0.000269431
+22,1,N,0.000275381
+23,1,N,0.000281807
+24,1,N,0.000288739
+25,1,N,0.000296211
+26,1,N,0.000304259
+27,1,N,0.000312921
+28,1,N,0.00032224
+29,1,N,0.000332264
+30,1,N,0.000343043
+31,1,N,0.000354634
+32,1,N,0.000367098
+33,1,N,0.000380501
+34,1,N,0.000394918
+35,1,N,0.000410429
+36,1,N,0.000427124
+37,1,N,0.000445098
+38,1,N,0.000464459
+39,1,N,0.000485326
+40,1,N,0.000507826
+41,1,N,0.000532104
+42,1,N,0.000558316
+43,1,N,0.000586636
+44,1,N,0.000617257
+45,1,N,0.000650391
+46,1,N,0.000686272
+47,1,N,0.000725161
+48,1,N,0.000767347
+49,1,N,0.00081315
+50,1,N,0.000862927
+51,1,N,0.000917074
+52,1,N,0.000976031
+53,1,N,0.00104029
+54,1,N,0.001110401
+55,1,N,0.001186975
+56,1,N,0.001270697
+57,1,N,0.001362335
+58,1,N,0.001462746
+59,1,N,0.001572895
+60,1,N,0.001693864
+61,1,N,0.001826868
+62,1,N,0.001973278
+63,1,N,0.002134635
+64,1,N,0.002312679
+65,1,N,0.002509375
+66,1,N,0.002726945
+67,1,N,0.002967904
+68,1,N,0.003235101
+69,1,N,0.00353177
+70,1,N,0.003861583
+71,1,N,0.004228719
+72,1,N,0.004637934
+73,1,N,0.005094653
+74,1,N,0.005605065
+75,1,N,0.006176246
+76,1,N,0.006816293
+77,1,N,0.007534484
+78,1,N,0.00834146
+79,1,N,0.009249445
+80,1,N,0.0102725
+81,1,N,0.011426815
+82,1,N,0.012731059
+83,1,N,0.014206785
+84,1,N,0.015878913
+85,1,N,0.017776286
+86,1,N,0.019932339
+87,1,N,0.022385876
+88,1,N,0.025181995
+89,1,N,0.028373179
+90,1,N,0.032020585
+18,2,N,0.000279591
+19,2,N,0.000284737
+20,2,N,0.000290323
+21,2,N,0.000296374
+22,2,N,0.000302919
+23,2,N,0.000309988
+24,2,N,0.000317613
+25,2,N,0.000325833
+26,2,N,0.000334685
+27,2,N,0.000344213
+28,2,N,0.000354464
+29,2,N,0.00036549
+30,2,N,0.000377348
+31,2,N,0.000390097
+32,2,N,0.000403807
+33,2,N,0.000418551
+34,2,N,0.00043441
+35,2,N,0.000451472
+36,2,N,0.000469836
+37,2,N,0.000489608
+38,2,N,0.000510905
+39,2,N,0.000533858
+40,2,N,0.000558609
+41,2,N,0.000585314
+42,2,N,0.000614148
+43,2,N,0.0006453
+44,2,N,0.000678983
+45,2,N,0.00071543
+46,2,N,0.000754899
+47,2,N,0.000797677
+48,2,N,0.000844082
+49,2,N,0.000894465
+50,2,N,0.00094922
+51,2,N,0.001008781
+52,2,N,0.001073634
+53,2,N,0.001144319
+54,2,N,0.001221441
+55,2,N,0.001305672
+56,2,N,0.001397767
+57,2,N,0.001498568
+58,2,N,0.001609021
+59,2,N,0.001730185
+60,2,N,0.00186325
+61,2,N,0.002009555
+62,2,N,0.002170605
+63,2,N,0.002348098
+64,2,N,0.002543947
+65,2,N,0.002760313
+66,2,N,0.00299964
+67,2,N,0.003264694
+68,2,N,0.003558611
+69,2,N,0.003884947
+70,2,N,0.004247741
+71,2,N,0.004651591
+72,2,N,0.005101728
+73,2,N,0.005604118
+74,2,N,0.006165571
+75,2,N,0.00679387
+76,2,N,0.007497923
+77,2,N,0.008287932
+78,2,N,0.009175606
+79,2,N,0.01017439
+80,2,N,0.01129975
+81,2,N,0.012569497
+82,2,N,0.014004165
+83,2,N,0.015627464
+84,2,N,0.017466804
+85,2,N,0.019553915
+86,2,N,0.021925573
+87,2,N,0.024624463
+88,2,N,0.027700195
+89,2,N,0.031210497
+90,2,N,0.035222643
+18,3,N,0.00030755
+19,3,N,0.000313211
+20,3,N,0.000319355
+21,3,N,0.000326012
+22,3,N,0.000333211
+23,3,N,0.000340986
+24,3,N,0.000349375
+25,3,N,0.000358416
+26,3,N,0.000368153
+27,3,N,0.000378634
+28,3,N,0.000389911
+29,3,N,0.000402039
+30,3,N,0.000415082
+31,3,N,0.000429107
+32,3,N,0.000444188
+33,3,N,0.000460406
+34,3,N,0.000477851
+35,3,N,0.00049662
+36,3,N,0.00051682
+37,3,N,0.000538568
+38,3,N,0.000561996
+39,3,N,0.000587244
+40,3,N,0.00061447
+41,3,N,0.000643846
+42,3,N,0.000675562
+43,3,N,0.00070983
+44,3,N,0.000746881
+45,3,N,0.000786973
+46,3,N,0.000830389
+47,3,N,0.000877445
+48,3,N,0.00092849
+49,3,N,0.000983912
+50,3,N,0.001044142
+51,3,N,0.001109659
+52,3,N,0.001180997
+53,3,N,0.001258751
+54,3,N,0.001343585
+55,3,N,0.001436239
+56,3,N,0.001537544
+57,3,N,0.001648425
+58,3,N,0.001769923
+59,3,N,0.001903203
+60,3,N,0.002049575
+61,3,N,0.002210511
+62,3,N,0.002387666
+63,3,N,0.002582908
+64,3,N,0.002798341
+65,3,N,0.003036344
+66,3,N,0.003299604
+67,3,N,0.003591164
+68,3,N,0.003914472
+69,3,N,0.004273441
+70,3,N,0.004672516
+71,3,N,0.00511675
+72,3,N,0.005611901
+73,3,N,0.00616453
+74,3,N,0.006782128
+75,3,N,0.007473258
+76,3,N,0.008247715
+77,3,N,0.009116725
+78,3,N,0.010093166
+79,3,N,0.011191829
+80,3,N,0.012429725
+81,3,N,0.013826446
+82,3,N,0.015404581
+83,3,N,0.01719021
+84,3,N,0.019213485
+85,3,N,0.021509306
+86,3,N,0.02411813
+87,3,N,0.02708691
+88,3,N,0.030470214
+89,3,N,0.034331546
+90,3,N,0.038744908
+18,4,N,0.000338305
+19,4,N,0.000344532
+20,4,N,0.000351291
+21,4,N,0.000358613
+22,4,N,0.000366532
+23,4,N,0.000375085
+24,4,N,0.000384312
+25,4,N,0.000394257
+26,4,N,0.000404968
+27,4,N,0.000416498
+28,4,N,0.000428902
+29,4,N,0.000442243
+30,4,N,0.000456591
+31,4,N,0.000472018
+32,4,N,0.000488607
+33,4,N,0.000506447
+34,4,N,0.000525636
+35,4,N,0.000546282
+36,4,N,0.000568502
+37,4,N,0.000592425
+38,4,N,0.000618195
+39,4,N,0.000645968
+40,4,N,0.000675917
+41,4,N,0.00070823
+42,4,N,0.000743119
+43,4,N,0.000780813
+44,4,N,0.000821569
+45,4,N,0.00086567
+46,4,N,0.000913428
+47,4,N,0.000965189
+48,4,N,0.001021339
+49,4,N,0.001082303
+50,4,N,0.001148556
+51,4,N,0.001220625
+52,4,N,0.001299097
+53,4,N,0.001384626
+54,4,N,0.001477943
+55,4,N,0.001579863
+56,4,N,0.001691298
+57,4,N,0.001813267
+58,4,N,0.001946915
+59,4,N,0.002093523
+60,4,N,0.002254533
+61,4,N,0.002431562
+62,4,N,0.002626433
+63,4,N,0.002841199
+64,4,N,0.003078175
+65,4,N,0.003339978
+66,4,N,0.003629564
+67,4,N,0.00395028
+68,4,N,0.004305919
+69,4,N,0.004700785
+70,4,N,0.005139767
+71,4,N,0.005628425
+72,4,N,0.006173091
+73,4,N,0.006780983
+74,4,N,0.007460341
+75,4,N,0.008220583
+76,4,N,0.009072486
+77,4,N,0.010028398
+78,4,N,0.011102483
+79,4,N,0.012311012
+80,4,N,0.013672698
+81,4,N,0.015209091
+82,4,N,0.016945039
+83,4,N,0.018909231
+84,4,N,0.021134833
+85,4,N,0.023660237
+86,4,N,0.026529943
+87,4,N,0.029795601
+88,4,N,0.033517235
+89,4,N,0.037764701
+90,4,N,0.042619399
+18,5,N,0.000372136
+19,5,N,0.000378985
+20,5,N,0.00038642
+21,5,N,0.000394474
+22,5,N,0.000403185
+23,5,N,0.000412594
+24,5,N,0.000422743
+25,5,N,0.000433683
+26,5,N,0.000445465
+27,5,N,0.000458147
+28,5,N,0.000471792
+29,5,N,0.000486468
+30,5,N,0.00050225
+31,5,N,0.00051922
+32,5,N,0.000537467
+33,5,N,0.000557092
+34,5,N,0.0005782
+35,5,N,0.00060091
+36,5,N,0.000625352
+37,5,N,0.000651668
+38,5,N,0.000680015
+39,5,N,0.000710565
+40,5,N,0.000743508
+41,5,N,0.000779053
+42,5,N,0.00081743
+43,5,N,0.000858894
+44,5,N,0.000903726
+45,5,N,0.000952237
+46,5,N,0.001004771
+47,5,N,0.001061708
+48,5,N,0.001123473
+49,5,N,0.001190533
+50,5,N,0.001263411
+51,5,N,0.001342687
+52,5,N,0.001429007
+53,5,N,0.001523089
+54,5,N,0.001625738
+55,5,N,0.00173785
+56,5,N,0.001860428
+57,5,N,0.001994594
+58,5,N,0.002141606
+59,5,N,0.002302876
+60,5,N,0.002479986
+61,5,N,0.002674718
+62,5,N,0.002889076
+63,5,N,0.003125319
+64,5,N,0.003385993
+65,5,N,0.003673976
+66,5,N,0.003992521
+67,5,N,0.004345308
+68,5,N,0.004736511
+69,5,N,0.005170864
+70,5,N,0.005653744
+71,5,N,0.006191268
+72,5,N,0.0067904
+73,5,N,0.007459081
+74,5,N,0.008206375
+75,5,N,0.009042642
+76,5,N,0.009979735
+77,5,N,0.011031238
+78,5,N,0.012212731
+79,5,N,0.013542113
+80,5,N,0.015039968
+81,5,N,0.01673
+82,5,N,0.018639543
+83,5,N,0.020800154
+84,5,N,0.023248316
+85,5,N,0.02602626
+86,5,N,0.029182937
+87,5,N,0.032775161
+88,5,N,0.036868959
+89,5,N,0.041541171
+90,5,N,0.046881338
+18,0,S,0.000462134
+19,0,S,0.00047064
+20,0,S,0.000479872
+21,0,S,0.000489874
+22,0,S,0.000500692
+23,0,S,0.000512376
+24,0,S,0.00052498
+25,0,S,0.000538566
+26,0,S,0.000553198
+27,0,S,0.000568946
+28,0,S,0.000585892
+29,0,S,0.000604116
+30,0,S,0.000623714
+31,0,S,0.00064479
+32,0,S,0.00066745
+33,0,S,0.00069182
+34,0,S,0.000718032
+35,0,S,0.000746236
+36,0,S,0.000776588
+37,0,S,0.000809268
+38,0,S,0.000844472
+39,0,S,0.00088241
+40,0,S,0.00092332
+41,0,S,0.000967462
+42,0,S,0.00101512
+43,0,S,0.001066612
+44,0,S,0.001122286
+45,0,S,0.001182528
+46,0,S,0.001247766
+47,0,S,0.001318474
+48,0,S,0.001395176
+49,0,S,0.001478454
+50,0,S,0.001568958
+51,0,S,0.001667406
+52,0,S,0.001774602
+53,0,S,0.001891436
+54,0,S,0.00201891
+55,0,S,0.002158136
+56,0,S,0.002310358
+57,0,S,0.002476972
+58,0,S,0.002659538
+59,0,S,0.00285981
+60,0,S,0.003079752
+61,0,S,0.003321578
+62,0,S,0.003587778
+63,0,S,0.003881154
+64,0,S,0.00420487
+65,0,S,0.0045625
+66,0,S,0.004958082
+67,0,S,0.00539619
+68,0,S,0.005882002
+69,0,S,0.0064214
+70,0,S,0.00702106
+71,0,S,0.00768858
+72,0,S,0.008432608
+73,0,S,0.009263004
+74,0,S,0.010191026
+75,0,S,0.011229538
+76,0,S,0.01239326
+77,0,S,0.013699062
+78,0,S,0.01516629
+79,0,S,0.016817174
+80,0,S,0.018677274
+81,0,S,0.020776028
+82,0,S,0.02314738
+83,0,S,0.025830518
+84,0,S,0.02887075
+85,0,S,0.03232052
+86,0,S,0.036240616
+87,0,S,0.040701592
+88,0,S,0.045785446
+89,0,S,0.051587598
+90,0,S,0.058219246
+18,1,S,0.000508348
+19,1,S,0.000517704
+20,1,S,0.00052786
+21,1,S,0.000538862
+22,1,S,0.000550762
+23,1,S,0.000563614
+24,1,S,0.000577478
+25,1,S,0.000592422
+26,1,S,0.000608518
+27,1,S,0.000625842
+28,1,S,0.00064448
+29,1,S,0.000664528
+30,1,S,0.000686086
+31,1,S,0.000709268
+32,1,S,0.000734196
+33,1,S,0.000761002
+34,1,S,0.000789836
+35,1,S,0.000820858
+36,1,S,0.000854248
+37,1,S,0.000890196
+38,1,S,0.000928918
+39,1,S,0.000970652
+40,1,S,0.001015652
+41,1,S,0.001064208
+42,1,S,0.001116632
+43,1,S,0.001173272
+44,1,S,0.001234514
+45,1,S,0.001300782
+46,1,S,0.001372544
+47,1,S,0.001450322
+48,1,S,0.001534694
+49,1,S,0.0016263
+50,1,S,0.001725854
+51,1,S,0.001834148
+52,1,S,0.001952062
+53,1,S,0.00208058
+54,1,S,0.002220802
+55,1,S,0.00237395
+56,1,S,0.002541394
+57,1,S,0.00272467
+58,1,S,0.002925492
+59,1,S,0.00314579
+60,1,S,0.003387728
+61,1,S,0.003653736
+62,1,S,0.003946556
+63,1,S,0.00426927
+64,1,S,0.004625358
+65,1,S,0.00501875
+66,1,S,0.00545389
+67,1,S,0.005935808
+68,1,S,0.006470202
+69,1,S,0.00706354
+70,1,S,0.007723166
+71,1,S,0.008457438
+72,1,S,0.009275868
+73,1,S,0.010189306
+74,1,S,0.01121013
+75,1,S,0.012352492
+76,1,S,0.013632586
+77,1,S,0.015068968
+78,1,S,0.01668292
+79,1,S,0.01849889
+80,1,S,0.020545
+81,1,S,0.02285363
+82,1,S,0.025462118
+83,1,S,0.02841357
+84,1,S,0.031757826
+85,1,S,0.035552572
+86,1,S,0.039864678
+87,1,S,0.044771752
+88,1,S,0.05036399
+89,1,S,0.056746358
+90,1,S,0.06404117
+18,2,S,0.000559182
+19,2,S,0.000569474
+20,2,S,0.000580646
+21,2,S,0.000592748
+22,2,S,0.000605838
+23,2,S,0.000619976
+24,2,S,0.000635226
+25,2,S,0.000651666
+26,2,S,0.00066937
+27,2,S,0.000688426
+28,2,S,0.000708928
+29,2,S,0.00073098
+30,2,S,0.000754696
+31,2,S,0.000780194
+32,2,S,0.000807614
+33,2,S,0.000837102
+34,2,S,0.00086882
+35,2,S,0.000902944
+36,2,S,0.000939672
+37,2,S,0.000979216
+38,2,S,0.00102181
+39,2,S,0.001067716
+40,2,S,0.001117218
+41,2,S,0.001170628
+42,2,S,0.001228296
+43,2,S,0.0012906
+44,2,S,0.001357966
+45,2,S,0.00143086
+46,2,S,0.001509798
+47,2,S,0.001595354
+48,2,S,0.001688164
+49,2,S,0.00178893
+50,2,S,0.00189844
+51,2,S,0.002017562
+52,2,S,0.002147268
+53,2,S,0.002288638
+54,2,S,0.002442882
+55,2,S,0.002611344
+56,2,S,0.002795534
+57,2,S,0.002997136
+58,2,S,0.003218042
+59,2,S,0.00346037
+60,2,S,0.0037265
+61,2,S,0.00401911
+62,2,S,0.00434121
+63,2,S,0.004696196
+64,2,S,0.005087894
+65,2,S,0.005520626
+66,2,S,0.00599928
+67,2,S,0.006529388
+68,2,S,0.007117222
+69,2,S,0.007769894
+70,2,S,0.008495482
+71,2,S,0.009303182
+72,2,S,0.010203456
+73,2,S,0.011208236
+74,2,S,0.012331142
+75,2,S,0.01358774
+76,2,S,0.014995846
+77,2,S,0.016575864
+78,2,S,0.018351212
+79,2,S,0.02034878
+80,2,S,0.0225995
+81,2,S,0.025138994
+82,2,S,0.02800833
+83,2,S,0.031254928
+84,2,S,0.034933608
+85,2,S,0.03910783
+86,2,S,0.043851146
+87,2,S,0.049248926
+88,2,S,0.05540039
+89,2,S,0.062420994
+90,2,S,0.070445286
+18,3,S,0.0006151
+19,3,S,0.000626422
+20,3,S,0.00063871
+21,3,S,0.000652024
+22,3,S,0.000666422
+23,3,S,0.000681972
+24,3,S,0.00069875
+25,3,S,0.000716832
+26,3,S,0.000736306
+27,3,S,0.000757268
+28,3,S,0.000779822
+29,3,S,0.000804078
+30,3,S,0.000830164
+31,3,S,0.000858214
+32,3,S,0.000888376
+33,3,S,0.000920812
+34,3,S,0.000955702
+35,3,S,0.00099324
+36,3,S,0.00103364
+37,3,S,0.001077136
+38,3,S,0.001123992
+39,3,S,0.001174488
+40,3,S,0.00122894
+41,3,S,0.001287692
+42,3,S,0.001351124
+43,3,S,0.00141966
+44,3,S,0.001493762
+45,3,S,0.001573946
+46,3,S,0.001660778
+47,3,S,0.00175489
+48,3,S,0.00185698
+49,3,S,0.001967824
+50,3,S,0.002088284
+51,3,S,0.002219318
+52,3,S,0.002361994
+53,3,S,0.002517502
+54,3,S,0.00268717
+55,3,S,0.002872478
+56,3,S,0.003075088
+57,3,S,0.00329685
+58,3,S,0.003539846
+59,3,S,0.003806406
+60,3,S,0.00409915
+61,3,S,0.004421022
+62,3,S,0.004775332
+63,3,S,0.005165816
+64,3,S,0.005596682
+65,3,S,0.006072688
+66,3,S,0.006599208
+67,3,S,0.007182328
+68,3,S,0.007828944
+69,3,S,0.008546882
+70,3,S,0.009345032
+71,3,S,0.0102335
+72,3,S,0.011223802
+73,3,S,0.01232906
+74,3,S,0.013564256
+75,3,S,0.014946516
+76,3,S,0.01649543
+77,3,S,0.01823345
+78,3,S,0.020186332
+79,3,S,0.022383658
+80,3,S,0.02485945
+81,3,S,0.027652892
+82,3,S,0.030809162
+83,3,S,0.03438042
+84,3,S,0.03842697
+85,3,S,0.043018612
+86,3,S,0.04823626
+87,3,S,0.05417382
+88,3,S,0.060940428
+89,3,S,0.068663092
+90,3,S,0.077489816
+18,4,S,0.00067661
+19,4,S,0.000689064
+20,4,S,0.000702582
+21,4,S,0.000717226
+22,4,S,0.000733064
+23,4,S,0.00075017
+24,4,S,0.000768624
+25,4,S,0.000788514
+26,4,S,0.000809936
+27,4,S,0.000832996
+28,4,S,0.000857804
+29,4,S,0.000884486
+30,4,S,0.000913182
+31,4,S,0.000944036
+32,4,S,0.000977214
+33,4,S,0.001012894
+34,4,S,0.001051272
+35,4,S,0.001092564
+36,4,S,0.001137004
+37,4,S,0.00118485
+38,4,S,0.00123639
+39,4,S,0.001291936
+40,4,S,0.001351834
+41,4,S,0.00141646
+42,4,S,0.001486238
+43,4,S,0.001561626
+44,4,S,0.001643138
+45,4,S,0.00173134
+46,4,S,0.001826856
+47,4,S,0.001930378
+48,4,S,0.002042678
+49,4,S,0.002164606
+50,4,S,0.002297112
+51,4,S,0.00244125
+52,4,S,0.002598194
+53,4,S,0.002769252
+54,4,S,0.002955886
+55,4,S,0.003159726
+56,4,S,0.003382596
+57,4,S,0.003626534
+58,4,S,0.00389383
+59,4,S,0.004187046
+60,4,S,0.004509066
+61,4,S,0.004863124
+62,4,S,0.005252866
+63,4,S,0.005682398
+64,4,S,0.00615635
+65,4,S,0.006679956
+66,4,S,0.007259128
+67,4,S,0.00790056
+68,4,S,0.008611838
+69,4,S,0.00940157
+70,4,S,0.010279534
+71,4,S,0.01125685
+72,4,S,0.012346182
+73,4,S,0.013561966
+74,4,S,0.014920682
+75,4,S,0.016441166
+76,4,S,0.018144972
+77,4,S,0.020056796
+78,4,S,0.022204966
+79,4,S,0.024622024
+80,4,S,0.027345396
+81,4,S,0.030418182
+82,4,S,0.033890078
+83,4,S,0.037818462
+84,4,S,0.042269666
+85,4,S,0.047320474
+86,4,S,0.053059886
+87,4,S,0.059591202
+88,4,S,0.06703447
+89,4,S,0.075529402
+90,4,S,0.085238798
+18,5,S,0.000744272
+19,5,S,0.00075797
+20,5,S,0.00077284
+21,5,S,0.000788948
+22,5,S,0.00080637
+23,5,S,0.000825188
+24,5,S,0.000845486
+25,5,S,0.000867366
+26,5,S,0.00089093
+27,5,S,0.000916294
+28,5,S,0.000943584
+29,5,S,0.000972936
+30,5,S,0.0010045
+31,5,S,0.00103844
+32,5,S,0.001074934
+33,5,S,0.001114184
+34,5,S,0.0011564
+35,5,S,0.00120182
+36,5,S,0.001250704
+37,5,S,0.001303336
+38,5,S,0.00136003
+39,5,S,0.00142113
+40,5,S,0.001487016
+41,5,S,0.001558106
+42,5,S,0.00163486
+43,5,S,0.001717788
+44,5,S,0.001807452
+45,5,S,0.001904474
+46,5,S,0.002009542
+47,5,S,0.002123416
+48,5,S,0.002246946
+49,5,S,0.002381066
+50,5,S,0.002526822
+51,5,S,0.002685374
+52,5,S,0.002858014
+53,5,S,0.003046178
+54,5,S,0.003251476
+55,5,S,0.0034757
+56,5,S,0.003720856
+57,5,S,0.003989188
+58,5,S,0.004283212
+59,5,S,0.004605752
+60,5,S,0.004959972
+61,5,S,0.005349436
+62,5,S,0.005778152
+63,5,S,0.006250638
+64,5,S,0.006771986
+65,5,S,0.007347952
+66,5,S,0.007985042
+67,5,S,0.008690616
+68,5,S,0.009473022
+69,5,S,0.010341728
+70,5,S,0.011307488
+71,5,S,0.012382536
+72,5,S,0.0135808
+73,5,S,0.014918162
+74,5,S,0.01641275
+75,5,S,0.018085284
+76,5,S,0.01995947
+77,5,S,0.022062476
+78,5,S,0.024425462
+79,5,S,0.027084226
+80,5,S,0.030079936
+81,5,S,0.03346
+82,5,S,0.037279086
+83,5,S,0.041600308
+84,5,S,0.046496632
+85,5,S,0.05205252
+86,5,S,0.058365874
+87,5,S,0.065550322
+88,5,S,0.073737918
+89,5,S,0.083082342
+90,5,S,0.093762676
diff --git a/src/heavylight/examples/template/model.py b/src/heavylight/examples/template/model.py
new file mode 100644
index 0000000..051779b
--- /dev/null
+++ b/src/heavylight/examples/template/model.py
@@ -0,0 +1,49 @@
+# a minimal example
+# %%
+import heavylight
+
+class ModelName(heavylight.Model):
+    def t(self, t):
+        return t
+    
+    # define user methods here
+    def net_cashflow(self, t):
+        """expected net cashflow occuring at time t"""
+        if t == 0:
+            return -100
+        else:
+            return self.monthly_premium(t) - 5
+    
+    def monthly_premium(self, t):
+        return self.data['annual_premium'] / 12
+    
+    def forward_rate_pm(self, t):
+        """monthly forward rate applying from time t-1 to time t (or however you define)"""
+        return (1 + self.forward_rate_pa) ** (1/12) - 1
+    
+    def v(self, t):
+        """discount factor from time t to time 0"""
+        if t == 0:
+            return 1.0
+        else:
+            return self.v(t - 1) / (1 + self.forward_rate_pm(t))
+    
+    def pv_net_cashflow(self, t):
+        """present value (at t=0) of the cashflow occuring at time t"""
+        return self.net_cashflow(t) * self.v(t)
+
+# data for model.
+
+data = {
+    'annual_premium': 84,
+}
+# run model, we could store forward rate in another dictionary (like data), but for simplicity we can pass it in directly.
+proj = ModelName(proj_len=5 * 12, data=data, forward_rate_pa=0.04)
+
+# output the present value
+print(f"Present Value of Net Cashflows: {proj.pv_net_cashflow.sum():=.2f}\n")
+
+# view a dataframe of the results
+print(proj.df.head())
+
+# %%
diff --git a/src/heavylight/make_examples.py b/src/heavylight/make_examples.py
new file mode 100644
index 0000000..575b0b6
--- /dev/null
+++ b/src/heavylight/make_examples.py
@@ -0,0 +1,25 @@
+# creates a folder with examples
+
+'''
+usage:
+```python
+import heavylight
+heavylight.make_example('example', 'path/to/folder')
+
+or using a simple CLI
+
+'''
+import shutil
+from pathlib import Path
+from typing import Union, Literal
+
+examples = ['template', 'protection']
+ExampleName = Literal['template', 'protection']
+
+def make_example(download_to_path: Union[str, Path], example_name: ExampleName):
+    if not example_name in examples:
+        raise ValueError(f'example {example_name} not in available examples {examples}')
+    example_path = Path(__file__).resolve().parent / 'examples' / example_name
+    dest_path = Path(download_to_path).resolve() / example_name
+    print(f'Creating {example_name} at {dest_path}')
+    shutil.copytree(src=example_path, dst=dest_path)
\ No newline at end of file


Test patch:
diff --git a/tests/test_examples.py b/tests/test_examples.py
new file mode 100644
index 0000000..9a3100e
--- /dev/null
+++ b/tests/test_examples.py
@@ -0,0 +1,14 @@
+from heavylight import make_example
+from pathlib import Path
+import shutil
+import pytest
+
+def test_make_example_bad():
+    with pytest.raises(ValueError):
+        make_example('temp', 'bad')
+
+def test_make_example_protection():
+    make_example('temp', 'protection')
+    assert (Path('temp') / 'protection').exists()
+    shutil.rmtree('temp/protection')
+