credit_tools 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.
- credit_tools-0.1.0/.github/workflows/docs.yml +47 -0
- credit_tools-0.1.0/.github/workflows/publish.yml +43 -0
- credit_tools-0.1.0/.gitignore +18 -0
- credit_tools-0.1.0/CONTRIBUTING/README.md +38 -0
- credit_tools-0.1.0/PKG-INFO +38 -0
- credit_tools-0.1.0/README.md +27 -0
- credit_tools-0.1.0/docs/examples/rating.ipynb +130 -0
- credit_tools-0.1.0/docs/index.ipynb +42 -0
- credit_tools-0.1.0/myst.yml +19 -0
- credit_tools-0.1.0/pyproject.toml +22 -0
- credit_tools-0.1.0/src/credit_tools/__init__.py +3 -0
- credit_tools-0.1.0/src/credit_tools/py.typed +0 -0
- credit_tools-0.1.0/src/credit_tools/rating/__init__.py +3 -0
- credit_tools-0.1.0/src/credit_tools/rating/core.py +64 -0
- credit_tools-0.1.0/src/credit_tools/resources/__init__.py +8 -0
- credit_tools-0.1.0/src/credit_tools/resources/fitch.json +23 -0
- credit_tools-0.1.0/src/credit_tools/resources/moody.json +23 -0
- credit_tools-0.1.0/uv.lock +1659 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: pages
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v3
|
|
24
|
+
|
|
25
|
+
- name: Install docs dependencies
|
|
26
|
+
run: uv sync --extra docs
|
|
27
|
+
|
|
28
|
+
- name: Build site
|
|
29
|
+
run: uv run jupyter-book build --execute --html --ci
|
|
30
|
+
env:
|
|
31
|
+
BASE_URL: /${{ github.event.repository.name }}
|
|
32
|
+
|
|
33
|
+
- uses: actions/configure-pages@v5
|
|
34
|
+
|
|
35
|
+
- uses: actions/upload-pages-artifact@v3
|
|
36
|
+
with:
|
|
37
|
+
path: _build/html
|
|
38
|
+
|
|
39
|
+
deploy:
|
|
40
|
+
needs: build
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
environment:
|
|
43
|
+
name: github-pages
|
|
44
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
45
|
+
steps:
|
|
46
|
+
- id: deployment
|
|
47
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v3
|
|
19
|
+
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: uv build
|
|
22
|
+
|
|
23
|
+
- uses: actions/upload-artifact@v4
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment:
|
|
32
|
+
name: pypi
|
|
33
|
+
url: https://pypi.org/project/credit_tools/
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
- uses: astral-sh/setup-uv@v3
|
|
41
|
+
|
|
42
|
+
- name: Publish to PyPI
|
|
43
|
+
run: uv publish
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Adding a rating scale resource
|
|
10
|
+
|
|
11
|
+
Rating scales live as plain data in [src/credit_tools/resources/](../src/credit_tools/resources/), one JSON file per scale (e.g. `moody.json`, `fitch.json`), mapping `{rating: expected_default_rate}`. `credit_tools.rating.assign_rating` is independent of any specific agency's scale — it just needs a dict like this. To add a new one, drop `resources/<name>.json` in and load it with `load_rating_scale("<name>")`.
|
|
12
|
+
|
|
13
|
+
## Docs
|
|
14
|
+
|
|
15
|
+
Examples live as Jupyter notebooks under [docs/](../docs/) and are built with [MyST/Jupyter Book v2](https://mystmd.org), configured by [myst.yml](../myst.yml) at the repo root.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv sync --extra docs
|
|
19
|
+
uv run jupyter-book build --execute --html # static site in _build/html
|
|
20
|
+
uv run jupyter-book start # live preview with hot reload
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`docs/` should only contain notebooks (no hand-written markdown/rst pages) — `myst.yml` is the one config file that lives outside it.
|
|
24
|
+
|
|
25
|
+
### Adding a new notebook
|
|
26
|
+
|
|
27
|
+
1. Add the `.ipynb` file under `docs/` (or `docs/examples/`).
|
|
28
|
+
2. Add an entry for it under `project.toc` in `myst.yml`, or regenerate the whole table of contents from what's on disk:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv run jupyter-book init --project --site --write-toc
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This only rewrites the `toc:` list — it won't touch metadata you've already filled in (title, description, authors). Run it from the repo root; it refuses to run from inside `docs/`.
|
|
35
|
+
|
|
36
|
+
Docs are rebuilt with `--execute`, so a notebook that no longer runs against the current code will fail CI — that's intentional, it's what keeps the examples honest.
|
|
37
|
+
|
|
38
|
+
Docs deploy to GitHub Pages automatically on push to `main` via [.github/workflows/docs.yml](../.github/workflows/docs.yml).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: credit_tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Out-of-the-box credit functionalities (risk rating, scoring, and more)
|
|
5
|
+
Author-email: Rafael Saraiva <rsa.saraiva94@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Provides-Extra: docs
|
|
9
|
+
Requires-Dist: jupyter-book>=1.0; extra == 'docs'
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# credit_tools
|
|
13
|
+
|
|
14
|
+
Multiple out-of-the-box functionalities for credit, installable via pip/uv.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install credit_tools
|
|
20
|
+
# or
|
|
21
|
+
uv add credit_tools
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
src/credit_tools/
|
|
28
|
+
rating/ # generic borrower-rating assignment (independent of any one scale)
|
|
29
|
+
resources/ # bundled rating scales as data, e.g. resources/moody.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Docs
|
|
33
|
+
|
|
34
|
+
Examples live as Jupyter notebooks in [docs/](docs/), published to GitHub Pages on every push to `main`: https://jumpingdino.github.io/credit_tools/
|
|
35
|
+
|
|
36
|
+
## Contributing
|
|
37
|
+
|
|
38
|
+
See [CONTRIBUTING/README.md](CONTRIBUTING/README.md) for dev setup, adding rating scale resources, and building/updating the docs.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# credit_tools
|
|
2
|
+
|
|
3
|
+
Multiple out-of-the-box functionalities for credit, installable via pip/uv.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install credit_tools
|
|
9
|
+
# or
|
|
10
|
+
uv add credit_tools
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Structure
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/credit_tools/
|
|
17
|
+
rating/ # generic borrower-rating assignment (independent of any one scale)
|
|
18
|
+
resources/ # bundled rating scales as data, e.g. resources/moody.json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Docs
|
|
22
|
+
|
|
23
|
+
Examples live as Jupyter notebooks in [docs/](docs/), published to GitHub Pages on every push to `main`: https://jumpingdino.github.io/credit_tools/
|
|
24
|
+
|
|
25
|
+
## Contributing
|
|
26
|
+
|
|
27
|
+
See [CONTRIBUTING/README.md](CONTRIBUTING/README.md) for dev setup, adding rating scale resources, and building/updating the docs.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# Rating assignment\n",
|
|
8
|
+
"\n",
|
|
9
|
+
"`credit_tools.rating.assign_rating` maps borrowers to rating buckets given:\n",
|
|
10
|
+
"\n",
|
|
11
|
+
"- a `borrower_id`, `ml_score`, and observed `defaulted` outcome per borrower, and\n",
|
|
12
|
+
"- a `rating_scale`: a `{rating: expected_default_rate}` dictionary.\n",
|
|
13
|
+
"\n",
|
|
14
|
+
"It fits a monotonic realized-default-rate curve against `ml_score` (higher score = higher risk), then maps each borrower's calibrated default probability to the rating with the closest expected default rate. The scale itself is just data — Moody's idealized default rates ship as a bundled resource, but any `{rating: edr}` mapping works."
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"cell_type": "code",
|
|
19
|
+
"execution_count": null,
|
|
20
|
+
"metadata": {},
|
|
21
|
+
"outputs": [],
|
|
22
|
+
"source": [
|
|
23
|
+
"import random\n",
|
|
24
|
+
"\n",
|
|
25
|
+
"from credit_tools.rating import assign_rating\n",
|
|
26
|
+
"from credit_tools.resources import load_rating_scale\n",
|
|
27
|
+
"\n",
|
|
28
|
+
"random.seed(0)"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"cell_type": "markdown",
|
|
33
|
+
"metadata": {},
|
|
34
|
+
"source": [
|
|
35
|
+
"## Load a rating scale\n",
|
|
36
|
+
"\n",
|
|
37
|
+
"`load_rating_scale` reads any bundled `resources/<name>.json` file."
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"cell_type": "code",
|
|
42
|
+
"execution_count": null,
|
|
43
|
+
"metadata": {},
|
|
44
|
+
"outputs": [],
|
|
45
|
+
"source": [
|
|
46
|
+
"scale = load_rating_scale(\"moody\")\n",
|
|
47
|
+
"dict(list(scale.items())[:5])"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"cell_type": "markdown",
|
|
52
|
+
"metadata": {},
|
|
53
|
+
"source": [
|
|
54
|
+
"## Simulate a borrower portfolio\n",
|
|
55
|
+
"\n",
|
|
56
|
+
"In practice `ml_scores` and `defaulted` come from your model's predictions and observed outcomes. Here we simulate them so the notebook is self-contained."
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"cell_type": "code",
|
|
61
|
+
"execution_count": null,
|
|
62
|
+
"metadata": {},
|
|
63
|
+
"outputs": [],
|
|
64
|
+
"source": [
|
|
65
|
+
"n = 2000\n",
|
|
66
|
+
"borrower_ids = [f\"b{i}\" for i in range(n)]\n",
|
|
67
|
+
"ml_scores = [random.random() for _ in range(n)]\n",
|
|
68
|
+
"defaulted = [random.random() < score for score in ml_scores]"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"cell_type": "markdown",
|
|
73
|
+
"metadata": {},
|
|
74
|
+
"source": [
|
|
75
|
+
"## Assign ratings"
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"cell_type": "code",
|
|
80
|
+
"execution_count": null,
|
|
81
|
+
"metadata": {},
|
|
82
|
+
"outputs": [],
|
|
83
|
+
"source": [
|
|
84
|
+
"ratings = assign_rating(borrower_ids, ml_scores, defaulted, scale)\n",
|
|
85
|
+
"{borrower_ids[i]: ratings[borrower_ids[i]] for i in range(5)}"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"cell_type": "markdown",
|
|
90
|
+
"metadata": {},
|
|
91
|
+
"source": [
|
|
92
|
+
"## Check calibration\n",
|
|
93
|
+
"\n",
|
|
94
|
+
"For each rating, the realized default rate among the borrowers assigned to it should track the target expected default rate from the scale."
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"cell_type": "code",
|
|
99
|
+
"execution_count": null,
|
|
100
|
+
"metadata": {},
|
|
101
|
+
"outputs": [],
|
|
102
|
+
"source": [
|
|
103
|
+
"from collections import defaultdict\n",
|
|
104
|
+
"\n",
|
|
105
|
+
"realized = defaultdict(list)\n",
|
|
106
|
+
"for i, borrower_id in enumerate(borrower_ids):\n",
|
|
107
|
+
" realized[ratings[borrower_id]].append(defaulted[i])\n",
|
|
108
|
+
"\n",
|
|
109
|
+
"for rating in sorted(realized, key=lambda r: scale[r]):\n",
|
|
110
|
+
" outcomes = realized[rating]\n",
|
|
111
|
+
" print(\n",
|
|
112
|
+
" f\"{rating:5s} target_edr={scale[rating]:.4f} n={len(outcomes):4d} \"\n",
|
|
113
|
+
" f\"realized_dr={sum(outcomes) / len(outcomes):.4f}\"\n",
|
|
114
|
+
" )"
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"metadata": {
|
|
119
|
+
"kernelspec": {
|
|
120
|
+
"display_name": "Python 3",
|
|
121
|
+
"language": "python",
|
|
122
|
+
"name": "python3"
|
|
123
|
+
},
|
|
124
|
+
"language_info": {
|
|
125
|
+
"name": "python"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"nbformat": 4,
|
|
129
|
+
"nbformat_minor": 5
|
|
130
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"metadata": {},
|
|
6
|
+
"source": [
|
|
7
|
+
"# credit_tools\n",
|
|
8
|
+
"\n",
|
|
9
|
+
"Out-of-the-box credit functionalities, installable via pip/uv.\n",
|
|
10
|
+
"\n",
|
|
11
|
+
"```bash\n",
|
|
12
|
+
"pip install credit_tools\n",
|
|
13
|
+
"```\n",
|
|
14
|
+
"\n",
|
|
15
|
+
"See the examples in the sidebar for each functionality, e.g. [rating assignment](examples/rating.ipynb)."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"cell_type": "code",
|
|
20
|
+
"execution_count": null,
|
|
21
|
+
"metadata": {},
|
|
22
|
+
"outputs": [],
|
|
23
|
+
"source": [
|
|
24
|
+
"import credit_tools\n",
|
|
25
|
+
"\n",
|
|
26
|
+
"credit_tools.__version__"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"metadata": {
|
|
31
|
+
"kernelspec": {
|
|
32
|
+
"display_name": "Python 3",
|
|
33
|
+
"language": "python",
|
|
34
|
+
"name": "python3"
|
|
35
|
+
},
|
|
36
|
+
"language_info": {
|
|
37
|
+
"name": "python"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"nbformat": 4,
|
|
41
|
+
"nbformat_minor": 5
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# See docs at: https://mystmd.org/guide/frontmatter
|
|
2
|
+
version: 1
|
|
3
|
+
project:
|
|
4
|
+
id: ad4e1e45-29ba-4bf5-917d-5f149e4469d5
|
|
5
|
+
title: credit_tools
|
|
6
|
+
description: Out-of-the-box credit functionalities
|
|
7
|
+
authors:
|
|
8
|
+
- name: Rafael Saraiva
|
|
9
|
+
github: https://github.com/JumpingDino/credit_tools
|
|
10
|
+
toc:
|
|
11
|
+
- file: docs/index.ipynb
|
|
12
|
+
- title: Examples
|
|
13
|
+
children:
|
|
14
|
+
- file: docs/examples/rating.ipynb
|
|
15
|
+
site:
|
|
16
|
+
template: book-theme
|
|
17
|
+
# options:
|
|
18
|
+
# favicon: favicon.ico
|
|
19
|
+
# logo: site_logo.png
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "credit_tools"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Out-of-the-box credit functionalities (risk rating, scoring, and more)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Rafael Saraiva", email = "rsa.saraiva94@gmail.com" }]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
docs = ["jupyter-book>=1.0"]
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.wheel]
|
|
19
|
+
packages = ["src/credit_tools"]
|
|
20
|
+
|
|
21
|
+
[tool.uv]
|
|
22
|
+
package = true
|
|
File without changes
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def assign_rating(
|
|
5
|
+
borrower_ids: Sequence[str],
|
|
6
|
+
ml_scores: Sequence[float],
|
|
7
|
+
defaulted: Sequence[bool],
|
|
8
|
+
rating_scale: dict[str, float],
|
|
9
|
+
) -> dict[str, str]:
|
|
10
|
+
if not (len(borrower_ids) == len(ml_scores) == len(defaulted)):
|
|
11
|
+
raise ValueError("borrower_ids, ml_scores, and defaulted must have the same length")
|
|
12
|
+
if not rating_scale:
|
|
13
|
+
raise ValueError("rating_scale must not be empty")
|
|
14
|
+
|
|
15
|
+
order = sorted(range(len(ml_scores)), key=lambda i: ml_scores[i])
|
|
16
|
+
sorted_scores = [ml_scores[i] for i in order]
|
|
17
|
+
sorted_targets = [float(defaulted[i]) for i in order]
|
|
18
|
+
|
|
19
|
+
calibrated_pd = _isotonic_regression(sorted_scores, sorted_targets)
|
|
20
|
+
ratings_by_edr = sorted(rating_scale.items(), key=lambda item: item[1])
|
|
21
|
+
|
|
22
|
+
assigned: dict[str, str] = {}
|
|
23
|
+
for i, pd in zip(order, calibrated_pd):
|
|
24
|
+
rating, _ = min(ratings_by_edr, key=lambda item: abs(item[1] - pd))
|
|
25
|
+
assigned[borrower_ids[i]] = rating
|
|
26
|
+
return assigned
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _isotonic_regression(sorted_scores: Sequence[float], sorted_targets: Sequence[float]) -> list[float]:
|
|
30
|
+
"""Pool-adjacent-violators fit of an increasing curve through (score, target)."""
|
|
31
|
+
unique_scores: list[float] = []
|
|
32
|
+
group_sums: list[float] = []
|
|
33
|
+
group_weights: list[float] = []
|
|
34
|
+
for score, target in zip(sorted_scores, sorted_targets):
|
|
35
|
+
if unique_scores and unique_scores[-1] == score:
|
|
36
|
+
group_sums[-1] += target
|
|
37
|
+
group_weights[-1] += 1
|
|
38
|
+
else:
|
|
39
|
+
unique_scores.append(score)
|
|
40
|
+
group_sums.append(target)
|
|
41
|
+
group_weights.append(1)
|
|
42
|
+
|
|
43
|
+
values = [s / w for s, w in zip(group_sums, group_weights)]
|
|
44
|
+
weights = list(group_weights)
|
|
45
|
+
sizes = [1] * len(values)
|
|
46
|
+
|
|
47
|
+
i = 0
|
|
48
|
+
while i < len(values) - 1:
|
|
49
|
+
if values[i] <= values[i + 1]:
|
|
50
|
+
i += 1
|
|
51
|
+
continue
|
|
52
|
+
merged_weight = weights[i] + weights[i + 1]
|
|
53
|
+
merged_value = (values[i] * weights[i] + values[i + 1] * weights[i + 1]) / merged_weight
|
|
54
|
+
values[i : i + 2] = [merged_value]
|
|
55
|
+
weights[i : i + 2] = [merged_weight]
|
|
56
|
+
sizes[i : i + 2] = [sizes[i] + sizes[i + 1]]
|
|
57
|
+
i = max(i - 1, 0)
|
|
58
|
+
|
|
59
|
+
fitted_by_group: list[float] = []
|
|
60
|
+
for value, size in zip(values, sizes):
|
|
61
|
+
fitted_by_group.extend([value] * size)
|
|
62
|
+
|
|
63
|
+
fitted_by_score = dict(zip(unique_scores, fitted_by_group))
|
|
64
|
+
return [fitted_by_score[score] for score in sorted_scores]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"AAA": 0.0001,
|
|
3
|
+
"AA+": 0.0002,
|
|
4
|
+
"AA": 0.0003,
|
|
5
|
+
"AA-": 0.0004,
|
|
6
|
+
"A+": 0.0006,
|
|
7
|
+
"A": 0.0008,
|
|
8
|
+
"A-": 0.0012,
|
|
9
|
+
"BBB+": 0.0020,
|
|
10
|
+
"BBB": 0.0030,
|
|
11
|
+
"BBB-": 0.0045,
|
|
12
|
+
"BB+": 0.0075,
|
|
13
|
+
"BB": 0.0110,
|
|
14
|
+
"BB-": 0.0170,
|
|
15
|
+
"B+": 0.0270,
|
|
16
|
+
"B": 0.0400,
|
|
17
|
+
"B-": 0.0600,
|
|
18
|
+
"CCC+": 0.0900,
|
|
19
|
+
"CCC": 0.1300,
|
|
20
|
+
"CCC-": 0.1900,
|
|
21
|
+
"CC": 0.3000,
|
|
22
|
+
"C": 0.5000
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Aaa": 0.0001,
|
|
3
|
+
"Aa1": 0.0002,
|
|
4
|
+
"Aa2": 0.0003,
|
|
5
|
+
"Aa3": 0.0004,
|
|
6
|
+
"A1": 0.0006,
|
|
7
|
+
"A2": 0.0008,
|
|
8
|
+
"A3": 0.0012,
|
|
9
|
+
"Baa1": 0.0020,
|
|
10
|
+
"Baa2": 0.0030,
|
|
11
|
+
"Baa3": 0.0045,
|
|
12
|
+
"Ba1": 0.0075,
|
|
13
|
+
"Ba2": 0.0110,
|
|
14
|
+
"Ba3": 0.0170,
|
|
15
|
+
"B1": 0.0270,
|
|
16
|
+
"B2": 0.0400,
|
|
17
|
+
"B3": 0.0600,
|
|
18
|
+
"Caa1": 0.0900,
|
|
19
|
+
"Caa2": 0.1300,
|
|
20
|
+
"Caa3": 0.1900,
|
|
21
|
+
"Ca": 0.3000,
|
|
22
|
+
"C": 0.5000
|
|
23
|
+
}
|