ins-pricing 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,6 +42,12 @@ _GNN_MPS_WARNED = False
42
42
  # Simplified GNN implementation.
43
43
  # =============================================================================
44
44
 
45
+ def _adj_mm(adj: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
46
+ """Matrix multiply that supports sparse or dense adjacency."""
47
+ if adj.is_sparse:
48
+ return torch.sparse.mm(adj, x)
49
+ return adj.matmul(x)
50
+
45
51
 
46
52
  class SimpleGraphLayer(nn.Module):
47
53
  def __init__(self, in_dim: int, out_dim: int, dropout: float = 0.1):
@@ -52,7 +58,7 @@ class SimpleGraphLayer(nn.Module):
52
58
 
53
59
  def forward(self, x: torch.Tensor, adj: torch.Tensor) -> torch.Tensor:
54
60
  # Message passing with normalized sparse adjacency: A_hat * X * W.
55
- h = torch.sparse.mm(adj, x)
61
+ h = _adj_mm(adj, x)
56
62
  h = self.linear(h)
57
63
  h = self.activation(h)
58
64
  return self.dropout(h)
@@ -86,7 +92,7 @@ class SimpleGNN(nn.Module):
86
92
  h = x
87
93
  for layer in self.layers:
88
94
  h = layer(h, adj_used)
89
- h = torch.sparse.mm(adj_used, h)
95
+ h = _adj_mm(adj_used, h)
90
96
  out = self.output(h)
91
97
  return self.output_act(out)
92
98
 
@@ -124,7 +130,11 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
124
130
  self.knn_gpu_mem_ratio = max(0.0, min(1.0, knn_gpu_mem_ratio))
125
131
  self.knn_gpu_mem_overhead = max(1.0, knn_gpu_mem_overhead)
126
132
  self.knn_cpu_jobs = knn_cpu_jobs
133
+ self.mps_dense_max_nodes = int(
134
+ os.environ.get("BAYESOPT_GNN_MPS_DENSE_MAX_NODES", "5000")
135
+ )
127
136
  self._knn_warning_emitted = False
137
+ self._mps_fallback_triggered = False
128
138
  self._adj_cache_meta: Optional[Dict[str, Any]] = None
129
139
  self._adj_cache_key: Optional[Tuple[Any, ...]] = None
130
140
  self._adj_cache_tensor: Optional[torch.Tensor] = None
@@ -168,11 +178,11 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
168
178
  else:
169
179
  self.device = torch.device('cuda')
170
180
  elif torch.backends.mps.is_available():
171
- self.device = torch.device('cpu')
181
+ self.device = torch.device('mps')
172
182
  global _GNN_MPS_WARNED
173
183
  if not _GNN_MPS_WARNED:
174
184
  print(
175
- "[GNN] MPS backend does not support sparse ops; falling back to CPU.",
185
+ "[GNN] Using MPS backend; will fall back to CPU on unsupported ops.",
176
186
  flush=True,
177
187
  )
178
188
  _GNN_MPS_WARNED = True
@@ -235,6 +245,41 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
235
245
  else:
236
246
  base.register_buffer("adj_buffer", adj)
237
247
 
248
+ @staticmethod
249
+ def _is_mps_unsupported_error(exc: BaseException) -> bool:
250
+ msg = str(exc).lower()
251
+ if "mps" not in msg:
252
+ return False
253
+ if any(token in msg for token in ("not supported", "not implemented", "does not support", "unimplemented", "out of memory")):
254
+ return True
255
+ return "sparse" in msg
256
+
257
+ def _fallback_to_cpu(self, reason: str) -> None:
258
+ if self.device.type != "mps" or self._mps_fallback_triggered:
259
+ return
260
+ self._mps_fallback_triggered = True
261
+ print(f"[GNN] MPS op unsupported ({reason}); falling back to CPU.", flush=True)
262
+ self.device = torch.device("cpu")
263
+ self.use_pyg_knn = False
264
+ self.data_parallel_enabled = False
265
+ self.ddp_enabled = False
266
+ base = self._unwrap_gnn()
267
+ try:
268
+ base = base.to(self.device)
269
+ except Exception:
270
+ pass
271
+ self.gnn = base
272
+ self.invalidate_graph_cache()
273
+
274
+ def _run_with_mps_fallback(self, fn, *args, **kwargs):
275
+ try:
276
+ return fn(*args, **kwargs)
277
+ except (RuntimeError, NotImplementedError) as exc:
278
+ if self.device.type == "mps" and self._is_mps_unsupported_error(exc):
279
+ self._fallback_to_cpu(str(exc))
280
+ return fn(*args, **kwargs)
281
+ raise
282
+
238
283
  def _graph_cache_meta(self, X_df: pd.DataFrame) -> Dict[str, Any]:
239
284
  row_hash = pd.util.hash_pandas_object(X_df, index=False).values
240
285
  idx_hash = pd.util.hash_pandas_object(X_df.index, index=False).values
@@ -255,11 +300,14 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
255
300
  "knn_gpu_mem_ratio": float(self.knn_gpu_mem_ratio),
256
301
  "knn_gpu_mem_overhead": float(self.knn_gpu_mem_overhead),
257
302
  }
303
+ adj_format = "dense" if self.device.type == "mps" else "sparse"
258
304
  return {
259
305
  "n_samples": int(X_df.shape[0]),
260
306
  "n_features": int(X_df.shape[1]),
261
307
  "hash": hasher.hexdigest(),
262
308
  "knn_config": knn_config,
309
+ "adj_format": adj_format,
310
+ "device_type": self.device.type,
263
311
  }
264
312
 
265
313
  def _graph_cache_key(self, X_df: pd.DataFrame) -> Tuple[Any, ...]:
@@ -284,8 +332,7 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
284
332
  if meta_expected is None:
285
333
  meta_expected = self._graph_cache_meta(X_df)
286
334
  try:
287
- payload = torch.load(self.graph_cache_path,
288
- map_location=self.device)
335
+ payload = torch.load(self.graph_cache_path, map_location="cpu")
289
336
  except Exception as exc:
290
337
  print(
291
338
  f"[GNN] Failed to load cached graph from {self.graph_cache_path}: {exc}")
@@ -293,7 +340,13 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
293
340
  if isinstance(payload, dict) and "adj" in payload:
294
341
  meta_cached = payload.get("meta")
295
342
  if meta_cached == meta_expected:
296
- return payload["adj"].to(self.device)
343
+ adj = payload["adj"]
344
+ if self.device.type == "mps" and getattr(adj, "is_sparse", False):
345
+ print(
346
+ f"[GNN] Cached sparse graph incompatible with MPS; rebuilding: {self.graph_cache_path}"
347
+ )
348
+ return None
349
+ return adj.to(self.device)
297
350
  print(
298
351
  f"[GNN] Cached graph metadata mismatch; rebuilding: {self.graph_cache_path}")
299
352
  return None
@@ -408,6 +461,11 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
408
461
  return True
409
462
 
410
463
  def _normalized_adj(self, edge_index: torch.Tensor, num_nodes: int) -> torch.Tensor:
464
+ if self.device.type == "mps":
465
+ return self._normalized_adj_dense(edge_index, num_nodes)
466
+ return self._normalized_adj_sparse(edge_index, num_nodes)
467
+
468
+ def _normalized_adj_sparse(self, edge_index: torch.Tensor, num_nodes: int) -> torch.Tensor:
411
469
  values = torch.ones(edge_index.shape[1], device=self.device)
412
470
  adj = torch.sparse_coo_tensor(
413
471
  edge_index.to(self.device), values, (num_nodes, num_nodes))
@@ -421,6 +479,21 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
421
479
  adj.indices(), norm_values, size=adj.shape)
422
480
  return adj_norm
423
481
 
482
+ def _normalized_adj_dense(self, edge_index: torch.Tensor, num_nodes: int) -> torch.Tensor:
483
+ if self.mps_dense_max_nodes <= 0 or num_nodes > self.mps_dense_max_nodes:
484
+ raise RuntimeError(
485
+ f"MPS dense adjacency not supported for {num_nodes} nodes; "
486
+ f"max={self.mps_dense_max_nodes}. Falling back to CPU."
487
+ )
488
+ edge_index = edge_index.to(self.device)
489
+ adj = torch.zeros((num_nodes, num_nodes), device=self.device, dtype=torch.float32)
490
+ adj[edge_index[0], edge_index[1]] = 1.0
491
+ deg = adj.sum(dim=1)
492
+ deg_inv_sqrt = torch.pow(deg + 1e-8, -0.5)
493
+ adj = adj * deg_inv_sqrt.view(-1, 1)
494
+ adj = adj * deg_inv_sqrt.view(1, -1)
495
+ return adj
496
+
424
497
  def _tensorize_split(self, X, y, w, allow_none: bool = False):
425
498
  if X is None and allow_none:
426
499
  return None, None, None
@@ -462,17 +535,25 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
462
535
  if self._adj_cache_meta == meta_expected and self._adj_cache_tensor is not None:
463
536
  cached = self._adj_cache_tensor
464
537
  if cached.device != self.device:
465
- cached = cached.to(self.device)
466
- self._adj_cache_tensor = cached
467
- return cached
538
+ if self.device.type == "mps" and getattr(cached, "is_sparse", False):
539
+ self._adj_cache_tensor = None
540
+ else:
541
+ cached = cached.to(self.device)
542
+ self._adj_cache_tensor = cached
543
+ if self._adj_cache_tensor is not None:
544
+ return self._adj_cache_tensor
468
545
  else:
469
546
  cache_key = self._graph_cache_key(X_df)
470
547
  if self._adj_cache_key == cache_key and self._adj_cache_tensor is not None:
471
548
  cached = self._adj_cache_tensor
472
549
  if cached.device != self.device:
473
- cached = cached.to(self.device)
474
- self._adj_cache_tensor = cached
475
- return cached
550
+ if self.device.type == "mps" and getattr(cached, "is_sparse", False):
551
+ self._adj_cache_tensor = None
552
+ else:
553
+ cached = cached.to(self.device)
554
+ self._adj_cache_tensor = cached
555
+ if self._adj_cache_tensor is not None:
556
+ return self._adj_cache_tensor
476
557
  X_np = None
477
558
  if X_tensor is None:
478
559
  X_np = X_df.to_numpy(dtype=np.float32, copy=False)
@@ -511,7 +592,20 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
511
592
  def fit(self, X_train, y_train, w_train=None,
512
593
  X_val=None, y_val=None, w_val=None,
513
594
  trial: Optional[optuna.trial.Trial] = None):
595
+ return self._run_with_mps_fallback(
596
+ self._fit_impl,
597
+ X_train,
598
+ y_train,
599
+ w_train,
600
+ X_val,
601
+ y_val,
602
+ w_val,
603
+ trial,
604
+ )
514
605
 
606
+ def _fit_impl(self, X_train, y_train, w_train=None,
607
+ X_val=None, y_val=None, w_val=None,
608
+ trial: Optional[optuna.trial.Trial] = None):
515
609
  X_train_tensor, y_train_tensor, w_train_tensor = self._tensorize_split(
516
610
  X_train, y_train, w_train, allow_none=False)
517
611
  has_val = X_val is not None and y_val is not None
@@ -621,6 +715,9 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
621
715
  self.best_epoch = int(best_epoch or self.epochs)
622
716
 
623
717
  def predict(self, X: pd.DataFrame) -> np.ndarray:
718
+ return self._run_with_mps_fallback(self._predict_impl, X)
719
+
720
+ def _predict_impl(self, X: pd.DataFrame) -> np.ndarray:
624
721
  self.gnn.eval()
625
722
  X_tensor, _, _ = self._tensorize_split(
626
723
  X, None, None, allow_none=False)
@@ -640,6 +737,9 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
640
737
  return y_pred.ravel()
641
738
 
642
739
  def encode(self, X: pd.DataFrame) -> np.ndarray:
740
+ return self._run_with_mps_fallback(self._encode_impl, X)
741
+
742
+ def _encode_impl(self, X: pd.DataFrame) -> np.ndarray:
643
743
  """Return per-sample node embeddings (hidden representations)."""
644
744
  base = self._unwrap_gnn()
645
745
  base.eval()
@@ -655,7 +755,7 @@ class GraphNeuralNetSklearn(TorchTrainerMixin, nn.Module):
655
755
  raise RuntimeError("GNN base module does not expose layers.")
656
756
  for layer in layers:
657
757
  h = layer(h, adj)
658
- h = torch.sparse.mm(adj, h)
758
+ h = _adj_mm(adj, h)
659
759
  return h.detach().cpu().numpy()
660
760
 
661
761
  def set_params(self, params: Dict[str, Any]):
@@ -27,6 +27,7 @@ from sklearn.preprocessing import StandardScaler
27
27
  from ..config_preprocess import BayesOptConfig, OutputManager
28
28
  from ..utils import DistributedUtils, EPS, ensure_parent_dir
29
29
  from ins_pricing.utils import get_logger, GPUMemoryManager, DeviceManager
30
+ from ins_pricing.utils.torch_compat import torch_load
30
31
 
31
32
  # Module-level logger
32
33
  _logger = get_logger("ins_pricing.trainer")
@@ -616,7 +617,7 @@ class TrainerBase:
616
617
  pass
617
618
  else:
618
619
  # FT-Transformer: load state_dict and reconstruct model
619
- loaded = torch.load(path, map_location='cpu', weights_only=False)
620
+ loaded = torch_load(path, map_location='cpu', weights_only=False)
620
621
  if isinstance(loaded, dict):
621
622
  if "state_dict" in loaded and "model_config" in loaded:
622
623
  # New format: state_dict + model_config
@@ -12,6 +12,7 @@ from .trainer_base import TrainerBase
12
12
  from ..models import GraphNeuralNetSklearn
13
13
  from ..utils import EPS
14
14
  from ins_pricing.utils import get_logger
15
+ from ins_pricing.utils.torch_compat import torch_load
15
16
 
16
17
  _logger = get_logger("ins_pricing.trainer.gnn")
17
18
 
@@ -300,7 +301,7 @@ class GNNTrainer(TrainerBase):
300
301
  if not os.path.exists(path):
301
302
  print(f"[load] Warning: Model file not found: {path}")
302
303
  return
303
- payload = torch.load(path, map_location='cpu', weights_only=False)
304
+ payload = torch_load(path, map_location='cpu', weights_only=False)
304
305
  if not isinstance(payload, dict):
305
306
  raise ValueError(f"Invalid GNN checkpoint: {path}")
306
307
  params = payload.get("best_params") or {}
@@ -322,4 +323,3 @@ class GNNTrainer(TrainerBase):
322
323
  self.model = model
323
324
  self.best_params = dict(params) if isinstance(params, dict) else None
324
325
  self.ctx.gnn_best = self.model
325
-
@@ -24,6 +24,7 @@ from .scoring import batch_score
24
24
  from ..modelling.core.bayesopt.models.model_gnn import GraphNeuralNetSklearn
25
25
  from ..modelling.core.bayesopt.models.model_resn import ResNetSklearn
26
26
  from ins_pricing.utils import DeviceManager, get_logger
27
+ from ins_pricing.utils.torch_compat import torch_load
27
28
 
28
29
  _logger = get_logger("ins_pricing.production.predict")
29
30
 
@@ -130,7 +131,7 @@ def _load_preprocess_from_model_file(
130
131
  if model_key in {"xgb", "glm"}:
131
132
  payload = joblib.load(model_path)
132
133
  else:
133
- payload = torch.load(model_path, map_location="cpu")
134
+ payload = torch_load(model_path, map_location="cpu")
134
135
  if isinstance(payload, dict):
135
136
  return payload.get("preprocess_artifacts")
136
137
  return None
@@ -261,7 +262,7 @@ def load_saved_model(
261
262
  return payload
262
263
 
263
264
  if model_key == "ft":
264
- payload = torch.load(model_path, map_location="cpu", weights_only=False)
265
+ payload = torch_load(model_path, map_location="cpu", weights_only=False)
265
266
  if isinstance(payload, dict):
266
267
  if "state_dict" in payload and "model_config" in payload:
267
268
  # New format: state_dict + model_config (DDP-safe)
@@ -325,7 +326,7 @@ def load_saved_model(
325
326
  if model_key == "resn":
326
327
  if input_dim is None:
327
328
  raise ValueError("input_dim is required for ResNet loading")
328
- payload = torch.load(model_path, map_location="cpu")
329
+ payload = torch_load(model_path, map_location="cpu")
329
330
  if isinstance(payload, dict) and "state_dict" in payload:
330
331
  state_dict = payload.get("state_dict")
331
332
  params = payload.get("best_params") or load_best_params(
@@ -351,7 +352,7 @@ def load_saved_model(
351
352
  if model_key == "gnn":
352
353
  if input_dim is None:
353
354
  raise ValueError("input_dim is required for GNN loading")
354
- payload = torch.load(model_path, map_location="cpu")
355
+ payload = torch_load(model_path, map_location="cpu")
355
356
  if not isinstance(payload, dict):
356
357
  raise ValueError(f"Invalid GNN checkpoint: {model_path}")
357
358
  params = payload.get("best_params") or {}
ins_pricing/setup.py CHANGED
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name="ins_pricing",
6
- version="0.3.1",
6
+ version="0.3.2",
7
7
  description="Reusable modelling, pricing, governance, and reporting utilities.",
8
8
  author="meishi125478",
9
9
  license="Proprietary",
@@ -0,0 +1,45 @@
1
+ """Torch compatibility helpers for 1.x and 2.x support."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from typing import Any, Optional
7
+
8
+ try:
9
+ import torch
10
+
11
+ TORCH_AVAILABLE = True
12
+ except ImportError: # pragma: no cover - handled by callers
13
+ TORCH_AVAILABLE = False
14
+ torch = None
15
+
16
+ _SUPPORTS_WEIGHTS_ONLY: Optional[bool] = None
17
+
18
+
19
+ def _supports_weights_only() -> bool:
20
+ """Check whether torch.load supports the weights_only argument."""
21
+ global _SUPPORTS_WEIGHTS_ONLY
22
+ if _SUPPORTS_WEIGHTS_ONLY is None:
23
+ if not TORCH_AVAILABLE:
24
+ _SUPPORTS_WEIGHTS_ONLY = False
25
+ else:
26
+ try:
27
+ sig = inspect.signature(torch.load)
28
+ _SUPPORTS_WEIGHTS_ONLY = "weights_only" in sig.parameters
29
+ except (TypeError, ValueError):
30
+ _SUPPORTS_WEIGHTS_ONLY = False
31
+ return bool(_SUPPORTS_WEIGHTS_ONLY)
32
+
33
+
34
+ def torch_load(
35
+ path: Any,
36
+ *args: Any,
37
+ weights_only: Optional[bool] = None,
38
+ **kwargs: Any,
39
+ ) -> Any:
40
+ """Load a torch artifact while handling 1.x/2.x API differences."""
41
+ if not TORCH_AVAILABLE:
42
+ raise RuntimeError("torch is required to load model files.")
43
+ if weights_only is not None and _supports_weights_only():
44
+ return torch.load(path, *args, weights_only=weights_only, **kwargs)
45
+ return torch.load(path, *args, **kwargs)
@@ -1,162 +1,162 @@
1
- Metadata-Version: 2.4
2
- Name: ins_pricing
3
- Version: 0.3.1
4
- Summary: Reusable modelling, pricing, governance, and reporting utilities.
5
- Author: meishi125478
6
- License: Proprietary
7
- Keywords: pricing,insurance,bayesopt,ml
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3 :: Only
10
- Classifier: Programming Language :: Python :: 3.9
11
- Classifier: License :: Other/Proprietary License
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Intended Audience :: Developers
14
- Requires-Python: >=3.9
15
- Description-Content-Type: text/markdown
16
- Requires-Dist: numpy>=1.20
17
- Requires-Dist: pandas>=1.4
18
- Provides-Extra: bayesopt
19
- Requires-Dist: torch>=1.13; extra == "bayesopt"
20
- Requires-Dist: optuna>=3.0; extra == "bayesopt"
21
- Requires-Dist: xgboost>=1.6; extra == "bayesopt"
22
- Requires-Dist: scikit-learn>=1.1; extra == "bayesopt"
23
- Requires-Dist: statsmodels>=0.13; extra == "bayesopt"
24
- Requires-Dist: joblib>=1.2; extra == "bayesopt"
25
- Requires-Dist: matplotlib>=3.5; extra == "bayesopt"
26
- Provides-Extra: plotting
27
- Requires-Dist: matplotlib>=3.5; extra == "plotting"
28
- Requires-Dist: scikit-learn>=1.1; extra == "plotting"
29
- Provides-Extra: explain
30
- Requires-Dist: torch>=1.13; extra == "explain"
31
- Requires-Dist: shap>=0.41; extra == "explain"
32
- Requires-Dist: scikit-learn>=1.1; extra == "explain"
33
- Provides-Extra: geo
34
- Requires-Dist: contextily>=1.3; extra == "geo"
35
- Requires-Dist: matplotlib>=3.5; extra == "geo"
36
- Provides-Extra: gnn
37
- Requires-Dist: torch>=1.13; extra == "gnn"
38
- Requires-Dist: pynndescent>=0.5; extra == "gnn"
39
- Requires-Dist: torch-geometric>=2.3; extra == "gnn"
40
- Provides-Extra: all
41
- Requires-Dist: torch>=1.13; extra == "all"
42
- Requires-Dist: optuna>=3.0; extra == "all"
43
- Requires-Dist: xgboost>=1.6; extra == "all"
44
- Requires-Dist: scikit-learn>=1.1; extra == "all"
45
- Requires-Dist: statsmodels>=0.13; extra == "all"
46
- Requires-Dist: joblib>=1.2; extra == "all"
47
- Requires-Dist: matplotlib>=3.5; extra == "all"
48
- Requires-Dist: shap>=0.41; extra == "all"
49
- Requires-Dist: contextily>=1.3; extra == "all"
50
- Requires-Dist: pynndescent>=0.5; extra == "all"
51
- Requires-Dist: torch-geometric>=2.3; extra == "all"
52
-
53
- # Insurance-Pricing
54
-
55
- A reusable toolkit for insurance modeling, pricing, governance, and reporting.
56
-
57
- ## Overview
58
-
59
- Insurance-Pricing (ins_pricing) is an enterprise-grade Python library designed for machine learning model training, pricing calculations, and model governance workflows in the insurance industry.
60
-
61
- ### Core Modules
62
-
63
- | Module | Description |
64
- |--------|-------------|
65
- | **modelling** | ML model training (GLM, XGBoost, ResNet, FT-Transformer, GNN) and model interpretability (SHAP, permutation importance) |
66
- | **pricing** | Factor table construction, numeric binning, premium calibration, exposure calculation, PSI monitoring |
67
- | **production** | Model prediction, batch scoring, data drift detection, production metrics monitoring |
68
- | **governance** | Model registry, version management, approval workflows, audit logging |
69
- | **reporting** | Report generation (Markdown format), report scheduling |
70
- | **utils** | Data validation, performance profiling, device management, logging configuration |
71
-
72
- ### Quick Start
73
-
74
- ```python
75
- # Model training with Bayesian optimization
76
- from ins_pricing import bayesopt as ropt
77
-
78
- model = ropt.BayesOptModel(
79
- train_data, test_data,
80
- model_name='my_model',
81
- resp_nme='target',
82
- weight_nme='weight',
83
- factor_nmes=feature_list,
84
- cate_list=categorical_features,
85
- )
86
- model.bayesopt_xgb(max_evals=100) # Train XGBoost
87
- model.bayesopt_resnet(max_evals=50) # Train ResNet
88
- model.bayesopt_ft(max_evals=50) # Train FT-Transformer
89
-
90
- # Pricing: build factor table
91
- from ins_pricing.pricing import build_factor_table
92
- factors = build_factor_table(
93
- df,
94
- factor_col='age_band',
95
- loss_col='claim_amount',
96
- exposure_col='exposure',
97
- )
98
-
99
- # Production: batch scoring
100
- from ins_pricing.production import batch_score
101
- scores = batch_score(model.trainers['xgb'].predict, df)
102
-
103
- # Model governance
104
- from ins_pricing.governance import ModelRegistry
105
- registry = ModelRegistry('models.json')
106
- registry.register(model_name, version, metrics=metrics)
107
- ```
108
-
109
- ### Project Structure
110
-
111
- ```
112
- ins_pricing/
113
- ├── cli/ # Command-line entry points
114
- ├── modelling/
115
- │ ├── core/bayesopt/ # ML model training core
116
- │ ├── explain/ # Model interpretability
117
- │ └── plotting/ # Model visualization
118
- ├── pricing/ # Insurance pricing module
119
- ├── production/ # Production deployment module
120
- ├── governance/ # Model governance
121
- ├── reporting/ # Report generation
122
- ├── utils/ # Utilities
123
- └── tests/ # Test suite
124
- ```
125
-
126
- ### Installation
127
-
128
- ```bash
129
- # Basic installation
130
- pip install ins_pricing
131
-
132
- # Full installation (all optional dependencies)
133
- pip install ins_pricing[all]
134
-
135
- # Install specific extras
136
- pip install ins_pricing[bayesopt] # Model training
137
- pip install ins_pricing[explain] # Model explanation
138
- pip install ins_pricing[plotting] # Visualization
139
- pip install ins_pricing[gnn] # Graph neural networks
140
- ```
141
-
142
- #### Multi-platform & GPU installation notes
143
-
144
- - **PyTorch (CPU/GPU/MPS)**: Install the correct PyTorch build for your platform/GPU first (CUDA on
145
- Linux/Windows, ROCm on supported AMD platforms, or MPS on Apple Silicon). Then install the
146
- optional extras you need (e.g., `bayesopt`, `explain`, or `gnn`). This avoids pip pulling a
147
- mismatched wheel.
148
- - **Torch Geometric (GNN)**: `torch-geometric` often requires platform-specific wheels (e.g.,
149
- `torch-scatter`, `torch-sparse`). Follow the official PyG installation instructions for your
150
- CUDA/ROCm/CPU environment, then install `ins_pricing[gnn]`.
151
- - **Multi-GPU**: Training code will use CUDA when available and can enable multi-GPU via
152
- `torch.distributed`/`DataParallel` where supported. On Windows, CUDA DDP is not supported and will
153
- fall back to single-GPU or DataParallel where possible.
154
-
155
- ### Requirements
156
-
157
- - Python >= 3.9
158
- - Core dependencies: numpy >= 1.20, pandas >= 1.4
159
-
160
- ### License
161
-
162
- Proprietary
1
+ Metadata-Version: 2.4
2
+ Name: ins_pricing
3
+ Version: 0.3.2
4
+ Summary: Reusable modelling, pricing, governance, and reporting utilities.
5
+ Author: meishi125478
6
+ License: Proprietary
7
+ Keywords: pricing,insurance,bayesopt,ml
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: License :: Other/Proprietary License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Developers
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: numpy>=1.20
17
+ Requires-Dist: pandas>=1.4
18
+ Provides-Extra: bayesopt
19
+ Requires-Dist: torch>=1.13; extra == "bayesopt"
20
+ Requires-Dist: optuna>=3.0; extra == "bayesopt"
21
+ Requires-Dist: xgboost>=1.6; extra == "bayesopt"
22
+ Requires-Dist: scikit-learn>=1.1; extra == "bayesopt"
23
+ Requires-Dist: statsmodels>=0.13; extra == "bayesopt"
24
+ Requires-Dist: joblib>=1.2; extra == "bayesopt"
25
+ Requires-Dist: matplotlib>=3.5; extra == "bayesopt"
26
+ Provides-Extra: plotting
27
+ Requires-Dist: matplotlib>=3.5; extra == "plotting"
28
+ Requires-Dist: scikit-learn>=1.1; extra == "plotting"
29
+ Provides-Extra: explain
30
+ Requires-Dist: torch>=1.13; extra == "explain"
31
+ Requires-Dist: shap>=0.41; extra == "explain"
32
+ Requires-Dist: scikit-learn>=1.1; extra == "explain"
33
+ Provides-Extra: geo
34
+ Requires-Dist: contextily>=1.3; extra == "geo"
35
+ Requires-Dist: matplotlib>=3.5; extra == "geo"
36
+ Provides-Extra: gnn
37
+ Requires-Dist: torch>=1.13; extra == "gnn"
38
+ Requires-Dist: pynndescent>=0.5; extra == "gnn"
39
+ Requires-Dist: torch-geometric>=2.3; extra == "gnn"
40
+ Provides-Extra: all
41
+ Requires-Dist: torch>=1.13; extra == "all"
42
+ Requires-Dist: optuna>=3.0; extra == "all"
43
+ Requires-Dist: xgboost>=1.6; extra == "all"
44
+ Requires-Dist: scikit-learn>=1.1; extra == "all"
45
+ Requires-Dist: statsmodels>=0.13; extra == "all"
46
+ Requires-Dist: joblib>=1.2; extra == "all"
47
+ Requires-Dist: matplotlib>=3.5; extra == "all"
48
+ Requires-Dist: shap>=0.41; extra == "all"
49
+ Requires-Dist: contextily>=1.3; extra == "all"
50
+ Requires-Dist: pynndescent>=0.5; extra == "all"
51
+ Requires-Dist: torch-geometric>=2.3; extra == "all"
52
+
53
+ # Insurance-Pricing
54
+
55
+ A reusable toolkit for insurance modeling, pricing, governance, and reporting.
56
+
57
+ ## Overview
58
+
59
+ Insurance-Pricing (ins_pricing) is an enterprise-grade Python library designed for machine learning model training, pricing calculations, and model governance workflows in the insurance industry.
60
+
61
+ ### Core Modules
62
+
63
+ | Module | Description |
64
+ |--------|-------------|
65
+ | **modelling** | ML model training (GLM, XGBoost, ResNet, FT-Transformer, GNN) and model interpretability (SHAP, permutation importance) |
66
+ | **pricing** | Factor table construction, numeric binning, premium calibration, exposure calculation, PSI monitoring |
67
+ | **production** | Model prediction, batch scoring, data drift detection, production metrics monitoring |
68
+ | **governance** | Model registry, version management, approval workflows, audit logging |
69
+ | **reporting** | Report generation (Markdown format), report scheduling |
70
+ | **utils** | Data validation, performance profiling, device management, logging configuration |
71
+
72
+ ### Quick Start
73
+
74
+ ```python
75
+ # Model training with Bayesian optimization
76
+ from ins_pricing import bayesopt as ropt
77
+
78
+ model = ropt.BayesOptModel(
79
+ train_data, test_data,
80
+ model_name='my_model',
81
+ resp_nme='target',
82
+ weight_nme='weight',
83
+ factor_nmes=feature_list,
84
+ cate_list=categorical_features,
85
+ )
86
+ model.bayesopt_xgb(max_evals=100) # Train XGBoost
87
+ model.bayesopt_resnet(max_evals=50) # Train ResNet
88
+ model.bayesopt_ft(max_evals=50) # Train FT-Transformer
89
+
90
+ # Pricing: build factor table
91
+ from ins_pricing.pricing import build_factor_table
92
+ factors = build_factor_table(
93
+ df,
94
+ factor_col='age_band',
95
+ loss_col='claim_amount',
96
+ exposure_col='exposure',
97
+ )
98
+
99
+ # Production: batch scoring
100
+ from ins_pricing.production import batch_score
101
+ scores = batch_score(model.trainers['xgb'].predict, df)
102
+
103
+ # Model governance
104
+ from ins_pricing.governance import ModelRegistry
105
+ registry = ModelRegistry('models.json')
106
+ registry.register(model_name, version, metrics=metrics)
107
+ ```
108
+
109
+ ### Project Structure
110
+
111
+ ```
112
+ ins_pricing/
113
+ ├── cli/ # Command-line entry points
114
+ ├── modelling/
115
+ │ ├── core/bayesopt/ # ML model training core
116
+ │ ├── explain/ # Model interpretability
117
+ │ └── plotting/ # Model visualization
118
+ ├── pricing/ # Insurance pricing module
119
+ ├── production/ # Production deployment module
120
+ ├── governance/ # Model governance
121
+ ├── reporting/ # Report generation
122
+ ├── utils/ # Utilities
123
+ └── tests/ # Test suite
124
+ ```
125
+
126
+ ### Installation
127
+
128
+ ```bash
129
+ # Basic installation
130
+ pip install ins_pricing
131
+
132
+ # Full installation (all optional dependencies)
133
+ pip install ins_pricing[all]
134
+
135
+ # Install specific extras
136
+ pip install ins_pricing[bayesopt] # Model training
137
+ pip install ins_pricing[explain] # Model explanation
138
+ pip install ins_pricing[plotting] # Visualization
139
+ pip install ins_pricing[gnn] # Graph neural networks
140
+ ```
141
+
142
+ #### Multi-platform & GPU installation notes
143
+
144
+ - **PyTorch (CPU/GPU/MPS)**: Install the correct PyTorch build for your platform/GPU first (CUDA on
145
+ Linux/Windows, ROCm on supported AMD platforms, or MPS on Apple Silicon). Then install the
146
+ optional extras you need (e.g., `bayesopt`, `explain`, or `gnn`). This avoids pip pulling a
147
+ mismatched wheel.
148
+ - **Torch Geometric (GNN)**: `torch-geometric` often requires platform-specific wheels (e.g.,
149
+ `torch-scatter`, `torch-sparse`). Follow the official PyG installation instructions for your
150
+ CUDA/ROCm/CPU environment, then install `ins_pricing[gnn]`.
151
+ - **Multi-GPU**: Training code will use CUDA when available and can enable multi-GPU via
152
+ `torch.distributed`/`DataParallel` where supported. On Windows, CUDA DDP is not supported and will
153
+ fall back to single-GPU or DataParallel where possible.
154
+
155
+ ### Requirements
156
+
157
+ - Python >= 3.9
158
+ - Core dependencies: numpy >= 1.20, pandas >= 1.4
159
+
160
+ ### License
161
+
162
+ Proprietary
@@ -3,7 +3,7 @@ ins_pricing/README.md,sha256=W4V2xtzM6pyQzwJPvWP7cNn-We9rxM8xrxRlBVQwoY8,3399
3
3
  ins_pricing/RELEASE_NOTES_0.2.8.md,sha256=KIJzk1jbZbZPKjwnkPSDHO_2Ipv3SP3CzCNDdf07jI0,9331
4
4
  ins_pricing/__init__.py,sha256=46j1wCdLVrgrofeBwKl-3NXTxzjbTv-w3KjW-dyKGiY,2622
5
5
  ins_pricing/exceptions.py,sha256=5fZavPV4zNJ7wPC75L215KkHXX9pRrfDAYZOdSKJMGo,4778
6
- ins_pricing/setup.py,sha256=ehvh_bSMMpl0uerZQMZXU-NKSYgV9dlDEFvGej2Hrq0,1702
6
+ ins_pricing/setup.py,sha256=ORpXP7CtmQeJRu2-QbRNzo96MuPT1vWY39-yUMydqd0,1702
7
7
  ins_pricing/cli/BayesOpt_entry.py,sha256=X3AiNQQh5ARcjVMM2vOKWPYPDIId40n_RPZA76pTGl4,558
8
8
  ins_pricing/cli/BayesOpt_incremental.py,sha256=_Klr5vvNoq_TbgwrH_T3f0a6cHmA9iVJMViiji6ahJY,35927
9
9
  ins_pricing/cli/Explain_Run.py,sha256=gEPQjqHiXyXlCTKjUzwSvbAn5_h74ABgb_sEGs-YHVE,664
@@ -45,13 +45,13 @@ ins_pricing/modelling/core/bayesopt/utils_backup.py,sha256=5RKizpR3j6KwR87WqqaXP
45
45
  ins_pricing/modelling/core/bayesopt/models/__init__.py,sha256=vFFCkGnO6rm50TbxR6QekKKQjq-NW4UFwog6fng8-p8,700
46
46
  ins_pricing/modelling/core/bayesopt/models/model_ft_components.py,sha256=0I0NiDf1D3cOhTRQwatsNTw9Julmxv5v3HZV8fTrvcQ,10989
47
47
  ins_pricing/modelling/core/bayesopt/models/model_ft_trainer.py,sha256=srBsI4KmijRC6s4G-XlVI9ahzjc2yquQxgdeICP1_4A,39253
48
- ins_pricing/modelling/core/bayesopt/models/model_gnn.py,sha256=l_oIgLeTJndfYR8lpZbqUq0MKH6wReE2z1B8n1E0P8k,28095
48
+ ins_pricing/modelling/core/bayesopt/models/model_gnn.py,sha256=1LdcuMwCRXsQcdj1Xjm8NlLjpX-cmunJlelkc2pfGB4,32400
49
49
  ins_pricing/modelling/core/bayesopt/models/model_resn.py,sha256=hAU77GcGC1mHbOOLfZ9vC5nhUhHlaPZmXjrkH3BrjKc,17128
50
50
  ins_pricing/modelling/core/bayesopt/trainers/__init__.py,sha256=ODYKjT-v4IDxu4ohGLCXY8r1-pMME9LAaNx6pmj5_38,481
51
- ins_pricing/modelling/core/bayesopt/trainers/trainer_base.py,sha256=xBo05nBYnV4crY7vQbugFkttABGKOh1qdpWILW8fazs,54961
51
+ ins_pricing/modelling/core/bayesopt/trainers/trainer_base.py,sha256=101Til7y9yuRQuFiYQHz9UGWmPghNiHHqmnNtQZxVok,55015
52
52
  ins_pricing/modelling/core/bayesopt/trainers/trainer_ft.py,sha256=jkafhvfEqIV_PYJ90e8kkOXVGvFpCKS0CRSKLvQ_elQ,34730
53
53
  ins_pricing/modelling/core/bayesopt/trainers/trainer_glm.py,sha256=wVU6F2Ubyu3IxP6K-epjkUTGOE8gKPCdpVxGW-JP9rM,7806
54
- ins_pricing/modelling/core/bayesopt/trainers/trainer_gnn.py,sha256=OChMeBELl_XWYZmZcpKq5BLY-srtTgempk4L5D0I7ys,13453
54
+ ins_pricing/modelling/core/bayesopt/trainers/trainer_gnn.py,sha256=G7an1PSRoJBWIfqZ_iIV5IOzeM03Z1amW4CPmFTklMU,13506
55
55
  ins_pricing/modelling/core/bayesopt/trainers/trainer_resn.py,sha256=y9KyKoUHLiKoXiX6XP4QGu0MKX5LyvlmSAT5tgn826M,11140
56
56
  ins_pricing/modelling/core/bayesopt/trainers/trainer_xgb.py,sha256=Ha3PtkB7AedlnRwWEfIBTEAPO69LY5lnjUrdGNL-yas,13783
57
57
  ins_pricing/modelling/core/bayesopt/utils/__init__.py,sha256=dbf4DrWOH4rABOuaZdBF7drYOBH5prjvM0TexT6DYyg,1911
@@ -82,7 +82,7 @@ ins_pricing/pricing/rate_table.py,sha256=llDW95i7gR6cCtGFwcGqgpgFvOOPCURaJWmuQw1
82
82
  ins_pricing/production/__init__.py,sha256=plUjyiwxrzHDDgXKezyGp9UHOg7Mav4f0ryXYtNmbfs,885
83
83
  ins_pricing/production/drift.py,sha256=q_oE_h2NbVETTBkh9QUu8Y68ERuFFcrfKpOb3zBcvsA,383
84
84
  ins_pricing/production/monitoring.py,sha256=A6Hyc5WSKhFkDZOIrqmFteuDee75CdcwdTq644vrk-U,4836
85
- ins_pricing/production/predict.py,sha256=mJog-RGLHxIJxx4oh0D1gbhJwBZPQs1z1P6YTDfWtfg,21791
85
+ ins_pricing/production/predict.py,sha256=fJoROPxW3474TrD4tnG6aEXXzoYVSZJd-kwOsUSEgQ8,21845
86
86
  ins_pricing/production/preprocess.py,sha256=cl20X0rVcKNCjVJswB8SdHffMgox6Qga4Ac29L6pW5g,9404
87
87
  ins_pricing/production/scoring.py,sha256=yFmMmbYb7w_RC4uZOCMnAjLMRcjXQWIuT1nsfu-bwuc,1379
88
88
  ins_pricing/reporting/README.md,sha256=kTVdB6pNewwh1HlCHrI2SzWTgprtQoQprLRQ2qLdgNA,486
@@ -118,8 +118,9 @@ ins_pricing/utils/logging.py,sha256=_AKB4ErmvygwGLtu7Ai7ESemj6Hh8FTgh4cs8j_gVW4,
118
118
  ins_pricing/utils/metrics.py,sha256=zhKYgXgki8RDscjP_GO2lEgzrtMIZCqOX_aLpQzdw6k,8390
119
119
  ins_pricing/utils/paths.py,sha256=o_tBiclFvBci4cYg9WANwKPxrMcglEdOjDP-EZgGjdQ,8749
120
120
  ins_pricing/utils/profiling.py,sha256=kmbykHLcYywlZxAf_aVU8HXID3zOvUcBoO5Q58AijhA,11132
121
+ ins_pricing/utils/torch_compat.py,sha256=ztTMmVxkQiNGvC5Ksz1VrzEA4QNZzRy72kW1MKIyA6M,1366
121
122
  ins_pricing/utils/validation.py,sha256=4Tw9VUJPk0N-WO3YUqZP-xXRl1Xpubkm0vi3WzzZrv4,13348
122
- ins_pricing-0.3.1.dist-info/METADATA,sha256=6zht9zTZNt7svuM5rDbLAvawAACRb24YlvswuaC9Ab0,6263
123
- ins_pricing-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
- ins_pricing-0.3.1.dist-info/top_level.txt,sha256=haZuNQpHKNBEPZx3NjLnHp8pV3I_J9QG8-HyJn00FA0,12
125
- ins_pricing-0.3.1.dist-info/RECORD,,
123
+ ins_pricing-0.3.2.dist-info/METADATA,sha256=K-Gae8gNhr87oAcVXOColnccx7J1CAH0SsfZeHd75vo,6101
124
+ ins_pricing-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
+ ins_pricing-0.3.2.dist-info/top_level.txt,sha256=haZuNQpHKNBEPZx3NjLnHp8pV3I_J9QG8-HyJn00FA0,12
126
+ ins_pricing-0.3.2.dist-info/RECORD,,