Update app.py
Browse files
app.py
CHANGED
|
@@ -4,119 +4,196 @@ import numpy as np
|
|
| 4 |
from numpy.random import default_rng
|
| 5 |
import io # For BytesIO to handle file in memory
|
| 6 |
|
| 7 |
-
# 1. Data Generation Function (
|
| 8 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
"""
|
| 10 |
-
Generates seriatim model points based on
|
| 11 |
-
from generate_model_points_for_cluster.py.
|
| 12 |
"""
|
| 13 |
-
rng = default_rng(
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
#
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
#
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
# Create DataFrame
|
| 37 |
data_dict = {
|
| 38 |
"age_at_entry": age_at_entry,
|
| 39 |
"sex": sex_col,
|
| 40 |
"policy_term": policy_term_col,
|
| 41 |
-
"policy_count":
|
| 42 |
"sum_assured": sum_assured_col,
|
| 43 |
"duration_mth": duration_mth_col
|
| 44 |
}
|
| 45 |
|
| 46 |
-
|
| 47 |
-
model_point_df = pd.DataFrame(data_dict, index=pd.RangeIndex(start=1, stop=MPCount + 1, name="policy_id"))
|
| 48 |
|
| 49 |
return model_point_df
|
| 50 |
|
| 51 |
# 2. Gradio App Definition
|
| 52 |
-
with gr.Blocks() as demo:
|
| 53 |
-
gr.Markdown("# Actuarial Model Points Generator
|
| 54 |
gr.Markdown(
|
| 55 |
-
"
|
| 56 |
-
"
|
| 57 |
-
"Click 'Generate Model Points' to view the table, then 'Download Excel' to save the data."
|
| 58 |
)
|
| 59 |
|
| 60 |
-
|
| 61 |
-
df_state = gr.State()
|
| 62 |
|
| 63 |
-
# UI Elements
|
| 64 |
with gr.Row():
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
# 3. Event Handlers
|
| 76 |
-
def handle_generate_button_click(
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
gr.Info("Generating model points... Please wait.")
|
| 82 |
-
df =
|
|
|
|
|
|
|
| 83 |
gr.Info(f"{len(df)} model points generated successfully!")
|
| 84 |
-
return df, df
|
| 85 |
|
| 86 |
def handle_download_button_click(current_df_to_download):
|
| 87 |
-
"""
|
| 88 |
-
Called when the 'Download Excel' button is clicked.
|
| 89 |
-
Prepares the DataFrame for download as an Excel file.
|
| 90 |
-
"""
|
| 91 |
if current_df_to_download is None or current_df_to_download.empty:
|
| 92 |
-
gr.Warning("No data available to download.
|
| 93 |
-
# Provide an empty Excel file to prevent download error if button is clicked prematurely
|
| 94 |
empty_excel_output = io.BytesIO()
|
| 95 |
pd.DataFrame().to_excel(empty_excel_output, index=False)
|
| 96 |
empty_excel_output.seek(0)
|
| 97 |
return empty_excel_output
|
| 98 |
|
| 99 |
excel_output = io.BytesIO()
|
| 100 |
-
# The DataFrame's index (policy_id) will be included by default
|
| 101 |
current_df_to_download.to_excel(excel_output, sheet_name='ModelPoints', engine='xlsxwriter', index=True)
|
| 102 |
excel_output.seek(0)
|
| 103 |
return excel_output
|
| 104 |
|
| 105 |
-
# Wire
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
generate_btn.click(
|
| 107 |
fn=handle_generate_button_click,
|
| 108 |
-
inputs=
|
| 109 |
outputs=[model_points_display, df_state]
|
| 110 |
)
|
| 111 |
|
| 112 |
download_excel_btn.click(
|
| 113 |
fn=handle_download_button_click,
|
| 114 |
-
inputs=[df_state],
|
| 115 |
-
outputs=[download_excel_btn]
|
| 116 |
)
|
| 117 |
|
| 118 |
-
#
|
| 119 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
if __name__ == "__main__":
|
|
|
|
| 4 |
from numpy.random import default_rng
|
| 5 |
import io # For BytesIO to handle file in memory
|
| 6 |
|
| 7 |
+
# 1. Data Generation Function (customizable via UI filters)
|
| 8 |
+
def generate_custom_model_points(
|
| 9 |
+
mp_count_val, seed_val, age_min_val, age_max_val,
|
| 10 |
+
sa_min_val, sa_max_val, policy_terms_selection_val,
|
| 11 |
+
include_sex_val, policy_count_fixed_val
|
| 12 |
+
):
|
| 13 |
"""
|
| 14 |
+
Generates seriatim model points based on user-defined parameters.
|
|
|
|
| 15 |
"""
|
| 16 |
+
rng = default_rng(int(seed_val))
|
| 17 |
+
mp_count_val = int(mp_count_val)
|
| 18 |
+
age_min_val = int(age_min_val)
|
| 19 |
+
age_max_val = int(age_max_val)
|
| 20 |
+
sa_min_val = float(sa_min_val)
|
| 21 |
+
sa_max_val = float(sa_max_val)
|
| 22 |
+
|
| 23 |
+
# Issue Age
|
| 24 |
+
age_at_entry = rng.integers(low=age_min_val, high=age_max_val + 1, size=mp_count_val)
|
| 25 |
+
|
| 26 |
+
# Sex
|
| 27 |
+
if include_sex_val:
|
| 28 |
+
sex_options = ["M", "F"]
|
| 29 |
+
sex_col = np.fromiter(map(lambda i: sex_options[i], rng.integers(low=0, high=len(sex_options), size=mp_count_val)), np.dtype('<U1'))
|
| 30 |
+
else:
|
| 31 |
+
sex_col = np.full(mp_count_val, "U") # Unknown/Unspecified if not included
|
| 32 |
+
|
| 33 |
+
# Policy Term
|
| 34 |
+
if not policy_terms_selection_val: # Default if user deselects all
|
| 35 |
+
policy_terms_selection_val = [10, 15, 20]
|
| 36 |
+
policy_term_options = np.array(policy_terms_selection_val).astype(int)
|
| 37 |
+
policy_term_col = rng.choice(policy_term_options, size=mp_count_val)
|
| 38 |
+
|
| 39 |
+
# Sum Assured
|
| 40 |
+
sum_assured_col = np.round((sa_max_val - sa_min_val) * rng.random(size=mp_count_val) + sa_min_val, -3)
|
| 41 |
+
|
| 42 |
+
# Duration in month: 1 <= Duration(mth) < Policy Term in month
|
| 43 |
+
# (policy_term_col * 12) is term in months.
|
| 44 |
+
# Max duration is (term_in_months - 1). Min duration is 1.
|
| 45 |
+
max_duration_val = policy_term_col * 12 - 1
|
| 46 |
+
# Ensure max_duration_val is at least 1 to prevent issues with rng.random if term is very short
|
| 47 |
+
max_duration_val = np.maximum(1, max_duration_val)
|
| 48 |
+
duration_mth_col = (rng.random(size=mp_count_val) * max_duration_val).astype(int) + 1
|
| 49 |
+
# Ensure duration_mth does not exceed max_duration_val (especially if max_duration_val was 1)
|
| 50 |
+
duration_mth_col = np.minimum(duration_mth_col, max_duration_val)
|
| 51 |
+
# And ensure it's at least 1
|
| 52 |
+
duration_mth_col = np.maximum(1, duration_mth_col)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# Policy Count
|
| 56 |
+
if policy_count_fixed_val:
|
| 57 |
+
policy_count_col_val = np.ones(mp_count_val, dtype=int)
|
| 58 |
+
else:
|
| 59 |
+
policy_count_col_val = rng.integers(low=1, high=101, size=mp_count_val) # Variable 1-100
|
| 60 |
|
| 61 |
# Create DataFrame
|
| 62 |
data_dict = {
|
| 63 |
"age_at_entry": age_at_entry,
|
| 64 |
"sex": sex_col,
|
| 65 |
"policy_term": policy_term_col,
|
| 66 |
+
"policy_count": policy_count_col_val,
|
| 67 |
"sum_assured": sum_assured_col,
|
| 68 |
"duration_mth": duration_mth_col
|
| 69 |
}
|
| 70 |
|
| 71 |
+
model_point_df = pd.DataFrame(data_dict, index=pd.RangeIndex(start=1, stop=mp_count_val + 1, name="policy_id"))
|
|
|
|
| 72 |
|
| 73 |
return model_point_df
|
| 74 |
|
| 75 |
# 2. Gradio App Definition
|
| 76 |
+
with gr.Blocks() as demo:
|
| 77 |
+
gr.Markdown("# Actuarial Model Points Generator")
|
| 78 |
gr.Markdown(
|
| 79 |
+
"Configure the parameters below to generate a custom set of seriatim model points. "
|
| 80 |
+
"The generated table can be viewed and downloaded as an Excel file."
|
|
|
|
| 81 |
)
|
| 82 |
|
| 83 |
+
df_state = gr.State() # To hold the generated DataFrame
|
|
|
|
| 84 |
|
|
|
|
| 85 |
with gr.Row():
|
| 86 |
+
with gr.Column(scale=1):
|
| 87 |
+
gr.Markdown("### Generation Parameters")
|
| 88 |
+
|
| 89 |
+
mp_count_input = gr.Slider(
|
| 90 |
+
minimum=100, maximum=50000, value=10000, step=100,
|
| 91 |
+
label="Number of Model Points"
|
| 92 |
+
)
|
| 93 |
+
seed_input = gr.Number(
|
| 94 |
+
value=12345, precision=0, label="Random Seed"
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
gr.Markdown("#### Age Parameters")
|
| 98 |
+
age_min_input = gr.Slider(
|
| 99 |
+
minimum=18, maximum=40, value=20, step=1, label="Minimum Age at Entry"
|
| 100 |
+
)
|
| 101 |
+
age_max_input = gr.Slider(
|
| 102 |
+
minimum=41, maximum=80, value=59, step=1, label="Maximum Age at Entry"
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
gr.Markdown("#### Sum Assured Parameters ($)")
|
| 106 |
+
sum_assured_min_input = gr.Number(
|
| 107 |
+
value=10000, label="Minimum Sum Assured"
|
| 108 |
+
)
|
| 109 |
+
sum_assured_max_input = gr.Number(
|
| 110 |
+
value=1000000, label="Maximum Sum Assured"
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
gr.Markdown("#### Policy Options")
|
| 114 |
+
policy_terms_input = gr.CheckboxGroup(
|
| 115 |
+
choices=[5, 10, 15, 20, 25, 30], value=[10, 15, 20],
|
| 116 |
+
label="Available Policy Terms (Years)"
|
| 117 |
+
)
|
| 118 |
+
include_sex_input = gr.Checkbox(
|
| 119 |
+
value=True, label="Include Sex (M/F)"
|
| 120 |
+
)
|
| 121 |
+
policy_count_fixed_input = gr.Checkbox(
|
| 122 |
+
value=True, label="Fixed Policy Count = 1 (Uncheck for variable count 1-100)"
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
generate_btn = gr.Button("Generate Model Points", variant="primary")
|
| 126 |
+
|
| 127 |
+
with gr.Column(scale=2):
|
| 128 |
+
model_points_display = gr.Dataframe(label="Generated Model Points")
|
| 129 |
+
download_excel_btn = gr.DownloadButton(
|
| 130 |
+
label="Download Excel",
|
| 131 |
+
value="model_points.xlsx", # Default filename
|
| 132 |
+
variant="secondary"
|
| 133 |
+
)
|
| 134 |
|
| 135 |
# 3. Event Handlers
|
| 136 |
+
def handle_generate_button_click(
|
| 137 |
+
mp_c, s, age_m, age_mx, sa_m, sa_mx, p_terms, incl_sex, pc_fixed
|
| 138 |
+
):
|
| 139 |
+
if int(age_m) >= int(age_mx):
|
| 140 |
+
gr.Warning("Minimum Age must be less than Maximum Age.")
|
| 141 |
+
return df_state.value, df_state.value # Keep current table and state
|
| 142 |
+
if float(sa_m) >= float(sa_mx):
|
| 143 |
+
gr.Warning("Minimum Sum Assured must be less than Maximum Sum Assured.")
|
| 144 |
+
return df_state.value, df_state.value
|
| 145 |
+
if not p_terms:
|
| 146 |
+
gr.Warning("No Policy Terms selected. Using defaults: [10, 15, 20].")
|
| 147 |
+
# Generation function will handle default if p_terms is empty list
|
| 148 |
+
|
| 149 |
gr.Info("Generating model points... Please wait.")
|
| 150 |
+
df = generate_custom_model_points(
|
| 151 |
+
mp_c, s, age_m, age_mx, sa_m, sa_mx, p_terms, incl_sex, pc_fixed
|
| 152 |
+
)
|
| 153 |
gr.Info(f"{len(df)} model points generated successfully!")
|
| 154 |
+
return df, df
|
| 155 |
|
| 156 |
def handle_download_button_click(current_df_to_download):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
if current_df_to_download is None or current_df_to_download.empty:
|
| 158 |
+
gr.Warning("No data available to download. Generate model points first.")
|
|
|
|
| 159 |
empty_excel_output = io.BytesIO()
|
| 160 |
pd.DataFrame().to_excel(empty_excel_output, index=False)
|
| 161 |
empty_excel_output.seek(0)
|
| 162 |
return empty_excel_output
|
| 163 |
|
| 164 |
excel_output = io.BytesIO()
|
|
|
|
| 165 |
current_df_to_download.to_excel(excel_output, sheet_name='ModelPoints', engine='xlsxwriter', index=True)
|
| 166 |
excel_output.seek(0)
|
| 167 |
return excel_output
|
| 168 |
|
| 169 |
+
# Wire up UI components to functions
|
| 170 |
+
inputs_list = [
|
| 171 |
+
mp_count_input, seed_input, age_min_input, age_max_input,
|
| 172 |
+
sum_assured_min_input, sum_assured_max_input, policy_terms_input,
|
| 173 |
+
include_sex_input, policy_count_fixed_input
|
| 174 |
+
]
|
| 175 |
+
|
| 176 |
generate_btn.click(
|
| 177 |
fn=handle_generate_button_click,
|
| 178 |
+
inputs=inputs_list,
|
| 179 |
outputs=[model_points_display, df_state]
|
| 180 |
)
|
| 181 |
|
| 182 |
download_excel_btn.click(
|
| 183 |
fn=handle_download_button_click,
|
| 184 |
+
inputs=[df_state],
|
| 185 |
+
outputs=[download_excel_btn]
|
| 186 |
)
|
| 187 |
|
| 188 |
+
# Optional: Load with default values on app start
|
| 189 |
+
# def initial_load():
|
| 190 |
+
# # Use default values from the input components
|
| 191 |
+
# # This would require getting their default values
|
| 192 |
+
# # For simplicity, we can call generate with fixed defaults or trigger a click
|
| 193 |
+
# # Or, just start with an empty table.
|
| 194 |
+
# gr.Info("App loaded. Configure parameters and click 'Generate'.")
|
| 195 |
+
# return None, None # Empty table and state
|
| 196 |
+
# demo.load(initial_load, outputs=[model_points_display, df_state])
|
| 197 |
|
| 198 |
|
| 199 |
if __name__ == "__main__":
|