open-rating-engine 0.1.0__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.
@@ -0,0 +1,67 @@
1
+ """open-rating-engine: a DAG-based insurance/banking rating engine.
2
+
3
+ Names below are loaded on first access (see ``_lazy.py``), so ``import
4
+ open_rating_engine`` is cheap and pulls in mlflow only once the pipeline or a layer
5
+ is actually used.
6
+ """
7
+
8
+ from importlib.metadata import PackageNotFoundError, version
9
+ from typing import TYPE_CHECKING
10
+
11
+ from ._lazy import attach
12
+
13
+ try:
14
+ __version__ = version("open-rating-engine")
15
+ except PackageNotFoundError: # running from a source checkout that isn't installed
16
+ __version__ = "0+unknown"
17
+
18
+ __getattr__, __dir__, _exports = attach(
19
+ __name__,
20
+ exports={
21
+ ".rating_engine.pricing_pipeline": ["PyfuncPricingPipeline"],
22
+ ".rating_engine.pricing_layers": [
23
+ "BasePricingLayer",
24
+ "ColumnCalculation",
25
+ "CustomLayer",
26
+ "DropColumns",
27
+ "GlmScoring",
28
+ "H2OScoringParameter",
29
+ "OnnxScoring",
30
+ "RatingTablesLayer",
31
+ "RenameColumns",
32
+ "SourceColumn",
33
+ "TableAggregation",
34
+ "TableDrop",
35
+ "TableJoin",
36
+ "TableTransform",
37
+ "DependencyError",
38
+ "LayerCalculationError",
39
+ ],
40
+ ".data_processing.data_aging": ["apply_aging", "AgingRuleError"],
41
+ },
42
+ submodules=["data_processing", "optimization", "rating_engine"],
43
+ )
44
+ __all__ = ["__version__", *_exports]
45
+
46
+ if TYPE_CHECKING:
47
+ from . import data_processing, optimization, rating_engine
48
+ from .data_processing.data_aging import AgingRuleError, apply_aging
49
+ from .rating_engine.pricing_layers import (
50
+ BasePricingLayer,
51
+ ColumnCalculation,
52
+ CustomLayer,
53
+ DependencyError,
54
+ DropColumns,
55
+ GlmScoring,
56
+ H2OScoringParameter,
57
+ LayerCalculationError,
58
+ OnnxScoring,
59
+ RatingTablesLayer,
60
+ RenameColumns,
61
+ SourceColumn,
62
+ TableAggregation,
63
+ TableDrop,
64
+ TableJoin,
65
+ TableTransform,
66
+ )
67
+ from .rating_engine.pricing_pipeline import PyfuncPricingPipeline
@@ -0,0 +1,58 @@
1
+ """Lazy re-exports for package ``__init__`` modules (PEP 562).
2
+
3
+ A package ``__init__`` that eagerly imports its submodules makes every import of
4
+ anything in the package pay for all of them: ``from open_rating_engine.rating_engine
5
+ .binning import ...`` would load mlflow just to run the parent ``__init__``. Instead
6
+ each ``__init__`` declares which names it re-exports from which submodule, and the
7
+ submodule is imported the first time one of those names is accessed.
8
+
9
+ Each ``__init__`` also repeats the re-exports under ``if TYPE_CHECKING:`` so IDEs and
10
+ type checkers see real imports. ``tests/test_public_api.py`` keeps the two in sync.
11
+ """
12
+
13
+ import sys
14
+ from importlib import import_module
15
+
16
+
17
+ def attach(package, exports, submodules=()):
18
+ """Return ``(__getattr__, __dir__, __all__)`` for ``package``.
19
+
20
+ Parameters
21
+ ----------
22
+ package : str
23
+ The package's ``__name__``.
24
+ exports : dict[str, list[str]]
25
+ Maps a module path relative to ``package`` (e.g. ``".pricing_layers"``) to
26
+ the public names re-exported from it.
27
+ submodules : iterable of str
28
+ Child module names (e.g. ``"optimization"``) made reachable as attributes,
29
+ so ``open_rating_engine.optimization`` works after a bare
30
+ ``import open_rating_engine``.
31
+ """
32
+ name_to_module = {}
33
+ for module, names in exports.items():
34
+ for name in names:
35
+ if name in name_to_module:
36
+ raise ValueError(
37
+ f"{package}: {name!r} is exported from both "
38
+ f"{name_to_module[name]!r} and {module!r}"
39
+ )
40
+ name_to_module[name] = module
41
+ submodules = frozenset(submodules)
42
+ __all__ = sorted(set(name_to_module) | submodules)
43
+
44
+ def __getattr__(name):
45
+ if name in name_to_module:
46
+ value = getattr(import_module(name_to_module[name], package), name)
47
+ elif name in submodules:
48
+ value = import_module(f".{name}", package)
49
+ else:
50
+ raise AttributeError(f"module {package!r} has no attribute {name!r}")
51
+ # Cache on the package so __getattr__ runs at most once per name.
52
+ setattr(sys.modules[package], name, value)
53
+ return value
54
+
55
+ def __dir__():
56
+ return list(__all__)
57
+
58
+ return __getattr__, __dir__, __all__
@@ -0,0 +1,13 @@
1
+ """Data preparation applied before scoring. Loaded lazily; see ``.._lazy``."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .._lazy import attach
6
+
7
+ __getattr__, __dir__, __all__ = attach(
8
+ __name__,
9
+ exports={".data_aging": ["apply_aging", "AgingRuleError"]},
10
+ )
11
+
12
+ if TYPE_CHECKING:
13
+ from .data_aging import AgingRuleError, apply_aging
@@ -0,0 +1,96 @@
1
+ """Aging preprocessor — applies SourceColumn aging rules to input frames.
2
+
3
+ One step forward in time per call. A single-cycle score path invokes this
4
+ once between ``input_frames = {table: pd.DataFrame(...)}`` construction
5
+ and ``pipeline.predict(...)``. A multi-year driver wraps the predict in a
6
+ loop and calls this once per cycle.
7
+
8
+ The function is pure (no DB, no I/O) and operates on dicts that the
9
+ caller has already resolved into ``layer_configs`` and ``input_frames``.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import numpy as np
14
+ import pandas as pd
15
+
16
+ __all__ = [
17
+ "apply_aging",
18
+ "AgingRuleError",
19
+ ]
20
+
21
+
22
+ SOURCE_COLUMN_LAYER_TYPE = "SourceColumn"
23
+
24
+
25
+ def apply_aging(
26
+ layer_configs: dict[str, dict],
27
+ input_frames: dict[str, pd.DataFrame],
28
+ ) -> dict[str, pd.DataFrame]:
29
+ """Mutate ``input_frames`` in place by applying each enabled SourceColumn
30
+ aging rule. Returns the same dict for chaining.
31
+
32
+ For every layer config of type ``SourceColumn`` with an
33
+ ``aging_rule.enabled == True`` and a non-empty ``expression``, the
34
+ expression is evaluated with ``{df, np, pd}`` in scope where ``df`` is
35
+ ``input_frames[source_table]``. The result is assigned to
36
+ ``df[source_column]`` in place. Layers without a rule, with the rule
37
+ disabled, or whose ``source_table`` is absent from ``input_frames`` are
38
+ silently skipped.
39
+
40
+ Order of evaluation within a single call is dict-insertion order.
41
+ Cross-column reads between aged columns are undefined within a cycle —
42
+ use a ``ColumnCalculation`` layer if you need ordering guarantees.
43
+ """
44
+ for layer_name, cfg in layer_configs.items():
45
+ if cfg.get("type") != SOURCE_COLUMN_LAYER_TYPE:
46
+ continue
47
+ rule = cfg.get("aging_rule")
48
+ if not isinstance(rule, dict):
49
+ continue
50
+ if not rule.get("enabled", True):
51
+ continue
52
+ expression = rule.get("expression")
53
+ if not isinstance(expression, str) or not expression:
54
+ continue
55
+
56
+ table = cfg.get("source_table")
57
+ column = cfg.get("source_column")
58
+ if not isinstance(table, str) or not table:
59
+ continue
60
+ if not isinstance(column, str) or not column:
61
+ continue
62
+ if table not in input_frames:
63
+ continue
64
+
65
+ df = input_frames[table]
66
+ try:
67
+ result = eval(expression, {"df": df, "np": np, "pd": pd})
68
+ except Exception as exc:
69
+ raise AgingRuleError(
70
+ f"aging rule on '{table}.{column}' raised: {exc}"
71
+ ) from exc
72
+
73
+ if not isinstance(result, pd.Series):
74
+ try:
75
+ result = pd.Series(result, index=df.index)
76
+ except Exception as exc:
77
+ raise AgingRuleError(
78
+ f"aging rule on '{table}.{column}' returned "
79
+ f"{type(result).__name__}; expected pandas Series or "
80
+ f"a scalar broadcastable to one ({exc})"
81
+ ) from exc
82
+
83
+ if len(result) != len(df):
84
+ raise AgingRuleError(
85
+ f"aging rule on '{table}.{column}' returned a Series of "
86
+ f"length {len(result)}; expected {len(df)} (matching df)"
87
+ )
88
+
89
+ df[column] = result
90
+
91
+ return input_frames
92
+
93
+
94
+ class AgingRuleError(ValueError):
95
+ """Raised when an aging expression fails to evaluate or returns an
96
+ incompatible shape."""
@@ -0,0 +1,35 @@
1
+ """Price optimisers that run on top of a pricing pipeline. Loaded lazily; see ``.._lazy``."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .._lazy import attach
6
+
7
+ __getattr__, __dir__, __all__ = attach(
8
+ __name__,
9
+ exports={
10
+ ".individual_price_optimizer": ["PriceOptimizerIndividual"],
11
+ ".rating_factor_price_optimizer": ["PriceOptimizerRatingFactor"],
12
+ ".multi_period_individual_price_optimizer": [
13
+ "PriceOptimizerIndividualMultiPeriod",
14
+ "make_log_multiplier_penalty",
15
+ ],
16
+ ".multi_period_rating_factor_price_optimizer": [
17
+ "PriceOptimizerRatingFactorMultiPeriod",
18
+ "make_log_multiplier_regularizer",
19
+ ],
20
+ ".simulate_adjustment_grid": ["simulate_adjustment_grid", "make_demand_emulation"],
21
+ },
22
+ )
23
+
24
+ if TYPE_CHECKING:
25
+ from .individual_price_optimizer import PriceOptimizerIndividual
26
+ from .multi_period_individual_price_optimizer import (
27
+ PriceOptimizerIndividualMultiPeriod,
28
+ make_log_multiplier_penalty,
29
+ )
30
+ from .multi_period_rating_factor_price_optimizer import (
31
+ PriceOptimizerRatingFactorMultiPeriod,
32
+ make_log_multiplier_regularizer,
33
+ )
34
+ from .rating_factor_price_optimizer import PriceOptimizerRatingFactor
35
+ from .simulate_adjustment_grid import make_demand_emulation, simulate_adjustment_grid