updatesupport-finance 0.1.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 updatesupport contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include LICENSE
2
+ include README.md
3
+ recursive-include examples *.py
@@ -0,0 +1,186 @@
1
+ Metadata-Version: 2.4
2
+ Name: updatesupport-finance
3
+ Version: 0.1.0
4
+ Summary: Financial model-risk extensions for updatesupport
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/nahuaque/updatesupport
7
+ Project-URL: Repository, https://github.com/nahuaque/updatesupport
8
+ Project-URL: Issues, https://github.com/nahuaque/updatesupport/issues
9
+ Keywords: credit-risk,expected-loss,financial-model-risk,model-validation,updatesupport
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Financial and Insurance Industry
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Office/Business :: Financial
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: updatesupport>=0.1.1
23
+ Dynamic: license-file
24
+
25
+ # updatesupport-finance
26
+
27
+ Financial model-risk extensions for
28
+ [`updatesupport`](https://pypi.org/project/updatesupport/).
29
+
30
+ `updatesupport-finance` audits whether a public risk segmentation is stable
31
+ enough to support a reported portfolio metric.
32
+
33
+ The core question is:
34
+
35
+ > If a model report only shows risk by coarse public buckets such as
36
+ > `product x region x FICO band x LTV band`, could the reported expected-loss
37
+ > estimate materially change if the hidden mix inside those buckets shifted?
38
+
39
+ This is a segmentation adequacy check for reported risk metrics. It is designed
40
+ for model-review and portfolio-monitoring artifacts, not as a replacement for
41
+ model validation, calibration, backtesting, or statistical uncertainty analysis.
42
+
43
+ Install directly:
44
+
45
+ ```bash
46
+ pip install updatesupport-finance
47
+ uv add updatesupport-finance
48
+ ```
49
+
50
+ Or through the core package extra:
51
+
52
+ ```bash
53
+ pip install "updatesupport[finance]"
54
+ uv add "updatesupport[finance]"
55
+ ```
56
+
57
+ The package provides finance-oriented row metrics, Q preset aliases, portfolio
58
+ compilation, and a model-risk report profile while keeping financial vocabulary
59
+ out of the core `updatesupport` package.
60
+
61
+ ## Why This Is Useful
62
+
63
+ Financial analysts already monitor model performance, population drift,
64
+ calibration, overrides, and scenario sensitivity. Those checks usually ask
65
+ whether the model or portfolio changed.
66
+
67
+ `updatesupport-finance` asks a different question:
68
+
69
+ > Is the reporting segmentation itself adequate for the metric being reported?
70
+
71
+ For example, a validation pack may report expected loss by:
72
+
73
+ - `product`
74
+ - `region`
75
+ - `fico_band`
76
+ - `ltv_band`
77
+
78
+ But inside those public buckets, hidden composition may vary by:
79
+
80
+ - broker channel
81
+ - employment type
82
+ - vintage
83
+ - hardship history
84
+ - documentation type
85
+ - local housing market
86
+ - borrower cashflow pattern
87
+
88
+ If those hidden subgroups have different expected-loss rates, the public
89
+ segmentation may not fully support the reported aggregate. The report quantifies
90
+ that hidden-composition ambiguity and identifies which hidden variables would
91
+ most improve the public segmentation.
92
+
93
+ ## What The Report Separates
94
+
95
+ The package is intentionally narrow. It separates:
96
+
97
+ - reported risk estimate: the supplied metric, such as expected loss or default
98
+ rate
99
+ - statistical uncertainty: confidence intervals or model uncertainty supplied by
100
+ other workflows
101
+ - hidden-composition ambiguity: how far the reported metric can move when hidden
102
+ mix shifts inside fixed public buckets
103
+ - refinement recommendations: hidden fields that would make the public
104
+ representation more stable
105
+
106
+ This is not a confidence interval and not a full model-risk-management system.
107
+ It is a reviewable control for one practical question: whether the reporting
108
+ representation is stable enough for the risk metric.
109
+
110
+ ## Analyst Workflow
111
+
112
+ 1. Choose public buckets from the model report.
113
+ 2. Choose hidden refinements that are available internally but not shown in the
114
+ public segmentation.
115
+ 3. Choose the target risk metric.
116
+ 4. Choose a plausible hidden-mix shift preset.
117
+ 5. Set a review threshold for hidden-composition ambiguity.
118
+ 6. Attach the generated Markdown report to a model-review or monitoring pack.
119
+
120
+ The review status is deliberately simple:
121
+
122
+ - `pass`: ambiguity and public adequacy checks are within the chosen thresholds
123
+ - `attention required`: the public segmentation may need refinement or explicit
124
+ acceptance of the ambiguity band
125
+
126
+ ## Example
127
+
128
+ ```python
129
+ import updatesupport_finance as usf
130
+
131
+ report = usf.model_risk_report(
132
+ portfolio,
133
+ public=["product", "region", "fico_band", "ltv_band"],
134
+ hidden=[
135
+ "product",
136
+ "region",
137
+ "fico_band",
138
+ "ltv_band",
139
+ "broker_channel",
140
+ "employment_type",
141
+ "vintage",
142
+ ],
143
+ metric=usf.expected_loss(pd="pd", lgd="lgd"),
144
+ exposure="ead",
145
+ q=usf.q_portfolio_mix_shift(radius=0.25),
146
+ model_id="EL_RETAIL_2026Q2",
147
+ portfolio_name="Retail credit portfolio",
148
+ as_of_date="2026-06-30",
149
+ intended_use="Expected-loss segmentation model review",
150
+ ambiguity_limit=0.0025,
151
+ public_adequacy_required=False,
152
+ )
153
+
154
+ print(report.to_markdown())
155
+ ```
156
+
157
+ The report answers:
158
+
159
+ - What is the reported portfolio risk estimate?
160
+ - What range is still possible under hidden mix shifts?
161
+ - Does the ambiguity exceed the review threshold?
162
+ - Which public buckets drive the instability?
163
+ - Which hidden fields are most valuable as public refinements?
164
+ - Which small public segmentation sits on the stability frontier, and why did
165
+ it beat nearby alternatives?
166
+
167
+ A synthetic portfolio example is available in `examples/model_risk_portfolio.py`
168
+ in the source repository:
169
+
170
+ ```bash
171
+ uv run --package updatesupport-finance python \
172
+ packages/updatesupport-finance/examples/model_risk_portfolio.py
173
+ ```
174
+
175
+ The example prints both the finance model-risk report and a core
176
+ `public_representation_frontier(...)` report for the same expected-loss metric.
177
+ The frontier section compares baseline versus selected ambiguity, close
178
+ dominated alternatives, and any screened-out refinement fields.
179
+
180
+ To write the Markdown report:
181
+
182
+ ```bash
183
+ uv run --package updatesupport-finance python \
184
+ packages/updatesupport-finance/examples/model_risk_portfolio.py \
185
+ --output data/finance_model_risk_report.md
186
+ ```
@@ -0,0 +1,162 @@
1
+ # updatesupport-finance
2
+
3
+ Financial model-risk extensions for
4
+ [`updatesupport`](https://pypi.org/project/updatesupport/).
5
+
6
+ `updatesupport-finance` audits whether a public risk segmentation is stable
7
+ enough to support a reported portfolio metric.
8
+
9
+ The core question is:
10
+
11
+ > If a model report only shows risk by coarse public buckets such as
12
+ > `product x region x FICO band x LTV band`, could the reported expected-loss
13
+ > estimate materially change if the hidden mix inside those buckets shifted?
14
+
15
+ This is a segmentation adequacy check for reported risk metrics. It is designed
16
+ for model-review and portfolio-monitoring artifacts, not as a replacement for
17
+ model validation, calibration, backtesting, or statistical uncertainty analysis.
18
+
19
+ Install directly:
20
+
21
+ ```bash
22
+ pip install updatesupport-finance
23
+ uv add updatesupport-finance
24
+ ```
25
+
26
+ Or through the core package extra:
27
+
28
+ ```bash
29
+ pip install "updatesupport[finance]"
30
+ uv add "updatesupport[finance]"
31
+ ```
32
+
33
+ The package provides finance-oriented row metrics, Q preset aliases, portfolio
34
+ compilation, and a model-risk report profile while keeping financial vocabulary
35
+ out of the core `updatesupport` package.
36
+
37
+ ## Why This Is Useful
38
+
39
+ Financial analysts already monitor model performance, population drift,
40
+ calibration, overrides, and scenario sensitivity. Those checks usually ask
41
+ whether the model or portfolio changed.
42
+
43
+ `updatesupport-finance` asks a different question:
44
+
45
+ > Is the reporting segmentation itself adequate for the metric being reported?
46
+
47
+ For example, a validation pack may report expected loss by:
48
+
49
+ - `product`
50
+ - `region`
51
+ - `fico_band`
52
+ - `ltv_band`
53
+
54
+ But inside those public buckets, hidden composition may vary by:
55
+
56
+ - broker channel
57
+ - employment type
58
+ - vintage
59
+ - hardship history
60
+ - documentation type
61
+ - local housing market
62
+ - borrower cashflow pattern
63
+
64
+ If those hidden subgroups have different expected-loss rates, the public
65
+ segmentation may not fully support the reported aggregate. The report quantifies
66
+ that hidden-composition ambiguity and identifies which hidden variables would
67
+ most improve the public segmentation.
68
+
69
+ ## What The Report Separates
70
+
71
+ The package is intentionally narrow. It separates:
72
+
73
+ - reported risk estimate: the supplied metric, such as expected loss or default
74
+ rate
75
+ - statistical uncertainty: confidence intervals or model uncertainty supplied by
76
+ other workflows
77
+ - hidden-composition ambiguity: how far the reported metric can move when hidden
78
+ mix shifts inside fixed public buckets
79
+ - refinement recommendations: hidden fields that would make the public
80
+ representation more stable
81
+
82
+ This is not a confidence interval and not a full model-risk-management system.
83
+ It is a reviewable control for one practical question: whether the reporting
84
+ representation is stable enough for the risk metric.
85
+
86
+ ## Analyst Workflow
87
+
88
+ 1. Choose public buckets from the model report.
89
+ 2. Choose hidden refinements that are available internally but not shown in the
90
+ public segmentation.
91
+ 3. Choose the target risk metric.
92
+ 4. Choose a plausible hidden-mix shift preset.
93
+ 5. Set a review threshold for hidden-composition ambiguity.
94
+ 6. Attach the generated Markdown report to a model-review or monitoring pack.
95
+
96
+ The review status is deliberately simple:
97
+
98
+ - `pass`: ambiguity and public adequacy checks are within the chosen thresholds
99
+ - `attention required`: the public segmentation may need refinement or explicit
100
+ acceptance of the ambiguity band
101
+
102
+ ## Example
103
+
104
+ ```python
105
+ import updatesupport_finance as usf
106
+
107
+ report = usf.model_risk_report(
108
+ portfolio,
109
+ public=["product", "region", "fico_band", "ltv_band"],
110
+ hidden=[
111
+ "product",
112
+ "region",
113
+ "fico_band",
114
+ "ltv_band",
115
+ "broker_channel",
116
+ "employment_type",
117
+ "vintage",
118
+ ],
119
+ metric=usf.expected_loss(pd="pd", lgd="lgd"),
120
+ exposure="ead",
121
+ q=usf.q_portfolio_mix_shift(radius=0.25),
122
+ model_id="EL_RETAIL_2026Q2",
123
+ portfolio_name="Retail credit portfolio",
124
+ as_of_date="2026-06-30",
125
+ intended_use="Expected-loss segmentation model review",
126
+ ambiguity_limit=0.0025,
127
+ public_adequacy_required=False,
128
+ )
129
+
130
+ print(report.to_markdown())
131
+ ```
132
+
133
+ The report answers:
134
+
135
+ - What is the reported portfolio risk estimate?
136
+ - What range is still possible under hidden mix shifts?
137
+ - Does the ambiguity exceed the review threshold?
138
+ - Which public buckets drive the instability?
139
+ - Which hidden fields are most valuable as public refinements?
140
+ - Which small public segmentation sits on the stability frontier, and why did
141
+ it beat nearby alternatives?
142
+
143
+ A synthetic portfolio example is available in `examples/model_risk_portfolio.py`
144
+ in the source repository:
145
+
146
+ ```bash
147
+ uv run --package updatesupport-finance python \
148
+ packages/updatesupport-finance/examples/model_risk_portfolio.py
149
+ ```
150
+
151
+ The example prints both the finance model-risk report and a core
152
+ `public_representation_frontier(...)` report for the same expected-loss metric.
153
+ The frontier section compares baseline versus selected ambiguity, close
154
+ dominated alternatives, and any screened-out refinement fields.
155
+
156
+ To write the Markdown report:
157
+
158
+ ```bash
159
+ uv run --package updatesupport-finance python \
160
+ packages/updatesupport-finance/examples/model_risk_portfolio.py \
161
+ --output data/finance_model_risk_report.md
162
+ ```
@@ -0,0 +1,326 @@
1
+ """Synthetic financial model-risk report example.
2
+
3
+ Run from the repository root with:
4
+
5
+ uv run --package updatesupport-finance python \
6
+ packages/updatesupport-finance/examples/model_risk_portfolio.py
7
+
8
+ Optionally write the Markdown report:
9
+
10
+ uv run --package updatesupport-finance python \
11
+ packages/updatesupport-finance/examples/model_risk_portfolio.py \
12
+ --output data/finance_model_risk_report.md
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+ import updatesupport as us
22
+ import updatesupport_finance as usf
23
+
24
+
25
+ PUBLIC_COLUMNS = ("product", "region", "fico_band", "ltv_band")
26
+ HIDDEN_COLUMNS = (
27
+ "product",
28
+ "region",
29
+ "fico_band",
30
+ "ltv_band",
31
+ "broker_channel",
32
+ "employment_type",
33
+ "vintage",
34
+ "hardship_history",
35
+ "documentation_type",
36
+ "local_housing_market",
37
+ "cashflow_pattern",
38
+ )
39
+ CANDIDATE_REFINEMENTS = (
40
+ "broker_channel",
41
+ "employment_type",
42
+ "vintage",
43
+ "hardship_history",
44
+ "documentation_type",
45
+ "local_housing_market",
46
+ "cashflow_pattern",
47
+ )
48
+
49
+
50
+ def synthetic_portfolio_rows() -> list[dict[str, Any]]:
51
+ """Return a small synthetic retail-credit portfolio.
52
+
53
+ The public reporting segmentation is intentionally coarse. Several hidden
54
+ subgroups live inside the same public buckets and have different expected
55
+ loss rates, which makes the representation-stability question visible.
56
+ """
57
+
58
+ return [
59
+ {
60
+ "account_count": 140,
61
+ "product": "mortgage",
62
+ "region": "north",
63
+ "fico_band": "prime",
64
+ "ltv_band": "low",
65
+ "broker_channel": "broker",
66
+ "employment_type": "salaried",
67
+ "vintage": "2024",
68
+ "hardship_history": "none",
69
+ "documentation_type": "full_doc",
70
+ "local_housing_market": "stable",
71
+ "cashflow_pattern": "stable",
72
+ "pd": 0.014,
73
+ "lgd": 0.32,
74
+ "ead": 15_400_000,
75
+ },
76
+ {
77
+ "account_count": 90,
78
+ "product": "mortgage",
79
+ "region": "north",
80
+ "fico_band": "prime",
81
+ "ltv_band": "low",
82
+ "broker_channel": "direct",
83
+ "employment_type": "self_employed",
84
+ "vintage": "2023",
85
+ "hardship_history": "prior",
86
+ "documentation_type": "alt_doc",
87
+ "local_housing_market": "cooling",
88
+ "cashflow_pattern": "seasonal",
89
+ "pd": 0.038,
90
+ "lgd": 0.46,
91
+ "ead": 8_100_000,
92
+ },
93
+ {
94
+ "account_count": 110,
95
+ "product": "mortgage",
96
+ "region": "north",
97
+ "fico_band": "prime",
98
+ "ltv_band": "medium",
99
+ "broker_channel": "broker",
100
+ "employment_type": "salaried",
101
+ "vintage": "2022",
102
+ "hardship_history": "none",
103
+ "documentation_type": "full_doc",
104
+ "local_housing_market": "cooling",
105
+ "cashflow_pattern": "stable",
106
+ "pd": 0.023,
107
+ "lgd": 0.39,
108
+ "ead": 12_650_000,
109
+ },
110
+ {
111
+ "account_count": 70,
112
+ "product": "mortgage",
113
+ "region": "north",
114
+ "fico_band": "prime",
115
+ "ltv_band": "medium",
116
+ "broker_channel": "correspondent",
117
+ "employment_type": "contractor",
118
+ "vintage": "2021",
119
+ "hardship_history": "prior",
120
+ "documentation_type": "full_doc",
121
+ "local_housing_market": "declining",
122
+ "cashflow_pattern": "volatile",
123
+ "pd": 0.049,
124
+ "lgd": 0.52,
125
+ "ead": 7_700_000,
126
+ },
127
+ {
128
+ "account_count": 85,
129
+ "product": "mortgage",
130
+ "region": "south",
131
+ "fico_band": "near_prime",
132
+ "ltv_band": "high",
133
+ "broker_channel": "broker",
134
+ "employment_type": "salaried",
135
+ "vintage": "2024",
136
+ "hardship_history": "none",
137
+ "documentation_type": "full_doc",
138
+ "local_housing_market": "stable",
139
+ "cashflow_pattern": "stable",
140
+ "pd": 0.058,
141
+ "lgd": 0.56,
142
+ "ead": 7_225_000,
143
+ },
144
+ {
145
+ "account_count": 65,
146
+ "product": "mortgage",
147
+ "region": "south",
148
+ "fico_band": "near_prime",
149
+ "ltv_band": "high",
150
+ "broker_channel": "correspondent",
151
+ "employment_type": "self_employed",
152
+ "vintage": "2022",
153
+ "hardship_history": "current",
154
+ "documentation_type": "alt_doc",
155
+ "local_housing_market": "declining",
156
+ "cashflow_pattern": "volatile",
157
+ "pd": 0.118,
158
+ "lgd": 0.67,
159
+ "ead": 5_525_000,
160
+ },
161
+ {
162
+ "account_count": 160,
163
+ "product": "auto",
164
+ "region": "west",
165
+ "fico_band": "prime",
166
+ "ltv_band": "medium",
167
+ "broker_channel": "dealer",
168
+ "employment_type": "salaried",
169
+ "vintage": "2024",
170
+ "hardship_history": "none",
171
+ "documentation_type": "full_doc",
172
+ "local_housing_market": "stable",
173
+ "cashflow_pattern": "stable",
174
+ "pd": 0.031,
175
+ "lgd": 0.50,
176
+ "ead": 3_840_000,
177
+ },
178
+ {
179
+ "account_count": 100,
180
+ "product": "auto",
181
+ "region": "west",
182
+ "fico_band": "prime",
183
+ "ltv_band": "medium",
184
+ "broker_channel": "online",
185
+ "employment_type": "contractor",
186
+ "vintage": "2023",
187
+ "hardship_history": "prior",
188
+ "documentation_type": "stated_income",
189
+ "local_housing_market": "stable",
190
+ "cashflow_pattern": "seasonal",
191
+ "pd": 0.061,
192
+ "lgd": 0.58,
193
+ "ead": 2_400_000,
194
+ },
195
+ {
196
+ "account_count": 190,
197
+ "product": "card",
198
+ "region": "east",
199
+ "fico_band": "near_prime",
200
+ "ltv_band": "na",
201
+ "broker_channel": "direct",
202
+ "employment_type": "salaried",
203
+ "vintage": "2024",
204
+ "hardship_history": "none",
205
+ "documentation_type": "full_doc",
206
+ "local_housing_market": "stable",
207
+ "cashflow_pattern": "stable",
208
+ "pd": 0.074,
209
+ "lgd": 0.82,
210
+ "ead": 1_900_000,
211
+ },
212
+ {
213
+ "account_count": 120,
214
+ "product": "card",
215
+ "region": "east",
216
+ "fico_band": "near_prime",
217
+ "ltv_band": "na",
218
+ "broker_channel": "affiliate",
219
+ "employment_type": "contractor",
220
+ "vintage": "2022",
221
+ "hardship_history": "current",
222
+ "documentation_type": "thin_file",
223
+ "local_housing_market": "cooling",
224
+ "cashflow_pattern": "volatile",
225
+ "pd": 0.132,
226
+ "lgd": 0.90,
227
+ "ead": 1_200_000,
228
+ },
229
+ ]
230
+
231
+
232
+ def build_report(
233
+ *,
234
+ q_radius: float = 0.35,
235
+ ambiguity_limit: float = 0.006,
236
+ ) -> usf.ModelRiskReport:
237
+ return usf.model_risk_report(
238
+ synthetic_portfolio_rows(),
239
+ public=PUBLIC_COLUMNS,
240
+ hidden=HIDDEN_COLUMNS,
241
+ metric=usf.expected_loss(pd="pd", lgd="lgd"),
242
+ exposure="ead",
243
+ candidate_refinements=CANDIDATE_REFINEMENTS,
244
+ q=usf.q_portfolio_mix_shift(radius=q_radius),
245
+ model_id="EL_SYNTHETIC_RETAIL_001",
246
+ portfolio_name="Synthetic retail credit portfolio",
247
+ as_of_date="2026-06-30",
248
+ intended_use="Expected-loss segmentation model review",
249
+ ambiguity_limit=ambiguity_limit,
250
+ public_adequacy_required=False,
251
+ top=5,
252
+ )
253
+
254
+
255
+ def build_frontier(
256
+ *,
257
+ q_radius: float = 0.35,
258
+ ambiguity_limit: float = 0.006,
259
+ ) -> us.PublicRepresentationFrontier:
260
+ """Choose the smallest stable public segmentation for the portfolio metric."""
261
+
262
+ return us.public_representation_frontier(
263
+ synthetic_portfolio_rows(),
264
+ base_public=PUBLIC_COLUMNS,
265
+ hidden=HIDDEN_COLUMNS,
266
+ target=usf.expected_loss(pd="pd", lgd="lgd"),
267
+ weight="ead",
268
+ candidate_refinements=CANDIDATE_REFINEMENTS,
269
+ q_presets=(
270
+ "saturated",
271
+ usf.q_portfolio_mix_shift(radius=q_radius),
272
+ "observed",
273
+ ),
274
+ min_cell_weights=(1.0,),
275
+ ambiguity_limit=ambiguity_limit,
276
+ bucket_budget=12,
277
+ search="beam",
278
+ beam_width=8,
279
+ max_added_columns=3,
280
+ max_evaluations=96,
281
+ title="Synthetic Finance Public Representation Frontier",
282
+ )
283
+
284
+
285
+ def main() -> None:
286
+ parser = argparse.ArgumentParser(
287
+ description="Generate a synthetic finance model-risk report.",
288
+ )
289
+ parser.add_argument(
290
+ "--output",
291
+ type=Path,
292
+ help="Optional Markdown output path.",
293
+ )
294
+ parser.add_argument(
295
+ "--q-radius",
296
+ type=float,
297
+ default=0.35,
298
+ help="Portfolio mix-shift radius for the bounded-shift Q preset.",
299
+ )
300
+ parser.add_argument(
301
+ "--ambiguity-limit",
302
+ type=float,
303
+ default=0.006,
304
+ help="Review threshold for hidden-composition ambiguity.",
305
+ )
306
+ args = parser.parse_args()
307
+
308
+ report = build_report(
309
+ q_radius=args.q_radius,
310
+ ambiguity_limit=args.ambiguity_limit,
311
+ )
312
+ frontier = build_frontier(
313
+ q_radius=args.q_radius,
314
+ ambiguity_limit=args.ambiguity_limit,
315
+ )
316
+ markdown = report.to_markdown() + "\n\n" + frontier.to_markdown()
317
+ if args.output is None:
318
+ print(markdown)
319
+ return
320
+ args.output.parent.mkdir(parents=True, exist_ok=True)
321
+ args.output.write_text(markdown + "\n", encoding="utf-8")
322
+ print(f"Wrote {args.output}")
323
+
324
+
325
+ if __name__ == "__main__":
326
+ main()