aggregate_api 1.0.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.
- aggregate_api-1.0.0/LICENSE +28 -0
- aggregate_api-1.0.0/PKG-INFO +187 -0
- aggregate_api-1.0.0/README.md +149 -0
- aggregate_api-1.0.0/pyproject.toml +178 -0
- aggregate_api-1.0.0/setup.cfg +4 -0
- aggregate_api-1.0.0/src/aggregate_api/__init__.py +41 -0
- aggregate_api-1.0.0/src/aggregate_api/__main__.py +154 -0
- aggregate_api-1.0.0/src/aggregate_api/app.py +206 -0
- aggregate_api-1.0.0/src/aggregate_api/audit.py +395 -0
- aggregate_api-1.0.0/src/aggregate_api/bounds.py +331 -0
- aggregate_api-1.0.0/src/aggregate_api/cache.py +319 -0
- aggregate_api-1.0.0/src/aggregate_api/capability.py +823 -0
- aggregate_api-1.0.0/src/aggregate_api/completion.py +219 -0
- aggregate_api-1.0.0/src/aggregate_api/config.py +363 -0
- aggregate_api-1.0.0/src/aggregate_api/cors.py +61 -0
- aggregate_api-1.0.0/src/aggregate_api/examples.py +620 -0
- aggregate_api-1.0.0/src/aggregate_api/layer_pricing.py +840 -0
- aggregate_api-1.0.0/src/aggregate_api/library.py +94 -0
- aggregate_api-1.0.0/src/aggregate_api/library_notes.py +96 -0
- aggregate_api-1.0.0/src/aggregate_api/models.py +1407 -0
- aggregate_api-1.0.0/src/aggregate_api/net.py +281 -0
- aggregate_api-1.0.0/src/aggregate_api/pnl.py +101 -0
- aggregate_api-1.0.0/src/aggregate_api/pricing.py +778 -0
- aggregate_api-1.0.0/src/aggregate_api/resources.py +257 -0
- aggregate_api-1.0.0/src/aggregate_api/routes/__init__.py +8 -0
- aggregate_api-1.0.0/src/aggregate_api/routes/decl.py +327 -0
- aggregate_api-1.0.0/src/aggregate_api/routes/examples.py +82 -0
- aggregate_api-1.0.0/src/aggregate_api/routes/meta.py +282 -0
- aggregate_api-1.0.0/src/aggregate_api/routes/objects.py +4119 -0
- aggregate_api-1.0.0/src/aggregate_api/routes/status.py +466 -0
- aggregate_api-1.0.0/src/aggregate_api/serializers.py +565 -0
- aggregate_api-1.0.0/src/aggregate_api/sessions.py +353 -0
- aggregate_api-1.0.0/src/aggregate_api/static/aggregate-api-logo-512.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/aggregate-api-logo.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/aggregate-api-trim.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/android-chrome-192x192.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/android-chrome-512x512.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/apple-touch-icon.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/bootstrap-icons-BeopsB42.woff +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/bootstrap-ohb1VZ53.js +5 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/codemirror-h62DHGGa.js +14 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/csv-grid.worker-DKzHGXac.js +4 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/echarts-B7o9sc00.js +40 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/echarts-gl-DG1Uf6wE.js +4282 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/lite-CUlcD8p4.css +1 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/lite-Dd2TnT4M.js +1 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/main-Bxhxa55v.css +9 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/main-CmoEiPit.js +9 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/tables-BHCF7qIF.js +8 -0
- aggregate_api-1.0.0/src/aggregate_api/static/assets/tables-CxvajLr7.css +1 -0
- aggregate_api-1.0.0/src/aggregate_api/static/favicon-16x16.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/favicon-32x32.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/favicon.ico +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/index.html +912 -0
- aggregate_api-1.0.0/src/aggregate_api/static/lite.html +83 -0
- aggregate_api-1.0.0/src/aggregate_api/static/logo.png +0 -0
- aggregate_api-1.0.0/src/aggregate_api/static/site.webmanifest +14 -0
- aggregate_api-1.0.0/src/aggregate_api/static/sw.js +78 -0
- aggregate_api-1.0.0/src/aggregate_api/status.py +536 -0
- aggregate_api-1.0.0/src/aggregate_api/status_page.html +546 -0
- aggregate_api-1.0.0/src/aggregate_api/tables.py +316 -0
- aggregate_api-1.0.0/src/aggregate_api.egg-info/PKG-INFO +187 -0
- aggregate_api-1.0.0/src/aggregate_api.egg-info/SOURCES.txt +88 -0
- aggregate_api-1.0.0/src/aggregate_api.egg-info/dependency_links.txt +1 -0
- aggregate_api-1.0.0/src/aggregate_api.egg-info/entry_points.txt +2 -0
- aggregate_api-1.0.0/src/aggregate_api.egg-info/requires.txt +14 -0
- aggregate_api-1.0.0/src/aggregate_api.egg-info/top_level.txt +1 -0
- aggregate_api-1.0.0/tests/test_audit.py +41 -0
- aggregate_api-1.0.0/tests/test_bounds.py +470 -0
- aggregate_api-1.0.0/tests/test_capability.py +515 -0
- aggregate_api-1.0.0/tests/test_cli.py +45 -0
- aggregate_api-1.0.0/tests/test_cors.py +44 -0
- aggregate_api-1.0.0/tests/test_decl.py +102 -0
- aggregate_api-1.0.0/tests/test_decl_parse.py +250 -0
- aggregate_api-1.0.0/tests/test_derive.py +753 -0
- aggregate_api-1.0.0/tests/test_entry_lock.py +160 -0
- aggregate_api-1.0.0/tests/test_examples.py +349 -0
- aggregate_api-1.0.0/tests/test_headless.py +75 -0
- aggregate_api-1.0.0/tests/test_layer_pricing.py +528 -0
- aggregate_api-1.0.0/tests/test_library.py +159 -0
- aggregate_api-1.0.0/tests/test_meta.py +137 -0
- aggregate_api-1.0.0/tests/test_objects.py +2363 -0
- aggregate_api-1.0.0/tests/test_packaging.py +84 -0
- aggregate_api-1.0.0/tests/test_plugins_meta.py +187 -0
- aggregate_api-1.0.0/tests/test_pnl_pentagon_route.py +181 -0
- aggregate_api-1.0.0/tests/test_pricing_exhibits.py +853 -0
- aggregate_api-1.0.0/tests/test_ruin_route.py +130 -0
- aggregate_api-1.0.0/tests/test_sessions.py +600 -0
- aggregate_api-1.0.0/tests/test_status.py +407 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Stephen J. Mildenhall
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aggregate_api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: aggregate Loss Lab (aLL): FastAPI service and single-page web app for the aggregate actuarial library.
|
|
5
|
+
Author-email: "Stephen J. Mildenhall" <steve@convexrisk.com>
|
|
6
|
+
Maintainer-email: "Stephen J. Mildenhall" <steve@convexrisk.com>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/mynl/aggregate_api
|
|
9
|
+
Project-URL: Source Code, https://github.com/mynl/aggregate_api
|
|
10
|
+
Project-URL: Documentation, https://github.com/mynl/aggregate_api#readme
|
|
11
|
+
Project-URL: Changelog, https://github.com/mynl/aggregate_api/blob/main/CHANGELOG.md
|
|
12
|
+
Project-URL: Issues, https://github.com/mynl/aggregate_api/issues
|
|
13
|
+
Keywords: actuarial,insurance,reinsurance,risk,aggregate loss,compound distribution,FFT,pricing,DecL
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: aggregate<2,>=1.0.1
|
|
26
|
+
Requires-Dist: greater-tables>=6.0.0
|
|
27
|
+
Requires-Dist: fastapi>=0.115
|
|
28
|
+
Requires-Dist: uvicorn[standard]>=0.30
|
|
29
|
+
Requires-Dist: pydantic>=2.7
|
|
30
|
+
Requires-Dist: pydantic-settings>=2.4
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
33
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
35
|
+
Provides-Extra: status
|
|
36
|
+
Requires-Dist: psutil>=5.9; extra == "status"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# aggregate_api
|
|
40
|
+
|
|
41
|
+
**aggregate Loss Lab** (aLL): a FastAPI service and a single-page web app for
|
|
42
|
+
the [`aggregate`](https://github.com/mynl/aggregate) actuarial library.
|
|
43
|
+
*Description to distribution.*
|
|
44
|
+
|
|
45
|
+
`aggregate_api` is the package; **aggregate Loss Lab** is the app it serves.
|
|
46
|
+
It puts `build()` behind an HTTP/JSON api (DecL parsing, FFT-based compound
|
|
47
|
+
distributions, plotting, and risk pricing) and ships a Bootstrap 5 and
|
|
48
|
+
CodeMirror 6 DecL workbench that runs against it. A single `aggregate-api`
|
|
49
|
+
process serves both the web UI (at `/`) and the JSON endpoints (under `/v1`)
|
|
50
|
+
same-origin.
|
|
51
|
+
|
|
52
|
+
> **Status:** 1.0.0, the first public release. Extracted from the `aggregate`
|
|
53
|
+
> repo so the library can ship without it. The package is installable and
|
|
54
|
+
> supported; the `/v1` surface is not yet a frozen interface, and the caveat at
|
|
55
|
+
> the end of [Running headless](#running-headless) says why. See
|
|
56
|
+
> [CHANGELOG.md](CHANGELOG.md) for what has landed and
|
|
57
|
+
> [dev/TODO.md](dev/TODO.md) for the roadmap.
|
|
58
|
+
|
|
59
|
+
## What's inside
|
|
60
|
+
|
|
61
|
+
- **Backend** (`src/aggregate_api/`): FastAPI app. Object lifecycle
|
|
62
|
+
(`POST /v1/objects` to build and cache, then `info`, `meta`, `summary`,
|
|
63
|
+
`tail_df`, `validation_df`, `stats_df`, `density_df`, `plot`, `kappa`,
|
|
64
|
+
`price`, `pricing_at`, plus `frame/{which}` in CSV or table-document form),
|
|
65
|
+
DecL helpers (`/v1/decl/complete`, `/lex`,
|
|
66
|
+
`/format`), and the example library read straight from `aggregate`'s recipe
|
|
67
|
+
base (`/v1/examples`, `/v1/examples/heroes`). In-memory LRU cache, build
|
|
68
|
+
timeout, SQLite audit log, CORS. Swagger UI at `/docs`.
|
|
69
|
+
- **Frontend** (`web/`): vanilla-JS SPA (Vite, Bootstrap, CodeMirror 6,
|
|
70
|
+
`csv-grid`). A DecL editor with syntax highlighting, autocomplete and history,
|
|
71
|
+
a landing gallery of showcase examples, tabbed risk output, interactive and
|
|
72
|
+
native plots, and a rich parse-error pane.
|
|
73
|
+
|
|
74
|
+
### Tables: one payload, two renderers
|
|
75
|
+
|
|
76
|
+
Every table under 500 rows is fetched once, as a **table document**: versioned
|
|
77
|
+
JSON carrying semantics only (dtypes, resolved formats, hierarchy, spans, flags)
|
|
78
|
+
and no widths or CSS. Both views render from that one document, so they cannot
|
|
79
|
+
disagree about a number:
|
|
80
|
+
|
|
81
|
+
- **Static** is `greater_tables`' own JS walker, which draws a book-quality table
|
|
82
|
+
(sparsified row index, spanned headers, partial rules). The walker and its
|
|
83
|
+
stylesheet are served straight out of the installed Python package at
|
|
84
|
+
`/v1/assets/`, so the renderer and the documents it renders can never version
|
|
85
|
+
skew.
|
|
86
|
+
- **Interactive** is `csv-grid`, fed by `irToGridInput(doc)`: sort, per-column
|
|
87
|
+
filter, fzf search, copy and save.
|
|
88
|
+
|
|
89
|
+
Which one you get is a page-wide preference in the header menu. Large frames (the
|
|
90
|
+
densities) skip the document entirely and go to the grid, which is the honest
|
|
91
|
+
instrument for them.
|
|
92
|
+
|
|
93
|
+
## Install
|
|
94
|
+
|
|
95
|
+
Requires Python ≥ 3.13. Nothing else: `aggregate` ≥ 1.0.1 and
|
|
96
|
+
`greater-tables` ≥ 6.0.0 come from PyPI with it.
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
pip install aggregate_api
|
|
100
|
+
aggregate-api --port 8001
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Then open http://127.0.0.1:8001/ for the app, or http://127.0.0.1:8001/docs for
|
|
104
|
+
the api.
|
|
105
|
+
|
|
106
|
+
> **Install the wheel, not the repository.** The web bundle is 2.5 MB of
|
|
107
|
+
> generated Vite output and is deliberately not in git, so it reaches you in the
|
|
108
|
+
> built distribution and only there. `pip install git+https://github.com/mynl/aggregate_api`
|
|
109
|
+
> installs a working api whose `/` returns 404, which is a reasonable thing to
|
|
110
|
+
> want if you are putting your own front end in front of the engine, and a
|
|
111
|
+
> baffling one otherwise. The wheel and sdist are also attached to each
|
|
112
|
+
> [GitHub release](https://github.com/mynl/aggregate_api/releases).
|
|
113
|
+
|
|
114
|
+
One footnote, which the pins above handle for you and which bites anyone
|
|
115
|
+
installing the table engine on its own: PyPI's `greater-tables` 5.3 is a
|
|
116
|
+
previous generation sharing the `greater_tables` import name, so a bare
|
|
117
|
+
`pip install greater-tables` can get a package with no `build`, `canonical_json`
|
|
118
|
+
or `IR_VERSION`, which fails at import rather than at install. This package pins
|
|
119
|
+
`>=6.0.0` and `tests/test_meta.py` asserts the right generation is present.
|
|
120
|
+
|
|
121
|
+
## Develop
|
|
122
|
+
|
|
123
|
+
Needs [`uv`](https://docs.astral.sh/uv/) and Node for the web build. A fresh
|
|
124
|
+
clone syncs with nothing checked out beside it.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
git clone https://github.com/mynl/aggregate_api
|
|
128
|
+
cd aggregate_api
|
|
129
|
+
uv sync --extra dev
|
|
130
|
+
.\scripts\build-web.ps1 # Windows; ./scripts/build-web.sh on Unix
|
|
131
|
+
uv run aggregate-api --port 8001 --reload
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`build-web` drops the bundle into `src/aggregate_api/static/`, which the FastAPI
|
|
135
|
+
`StaticFiles` mount serves at `/`. Until it has run once, `/` has nothing to
|
|
136
|
+
serve. For hot reload with a Vite dev server proxying `/v1` to `:8000`, use
|
|
137
|
+
`cd web; npm install; npm run dev` instead.
|
|
138
|
+
|
|
139
|
+
To develop against a local checkout of `aggregate` or `greater-tables` rather
|
|
140
|
+
than the released build, install it editable over the synced environment:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
uv pip install -e ../aggregate # or wherever the checkout is
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The next `uv sync` silently reinstalls the released build, so `uv run --no-sync`
|
|
147
|
+
is the way to work in between.
|
|
148
|
+
|
|
149
|
+
Run the tests. There are two suites and both are the gate:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
uv run pytest # 490 tests, FastAPI TestClient, no live server needed
|
|
153
|
+
cd web; npm test # 277 tests, node --test over web/test/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Rehearse a release, which is a stronger check than either suite because it
|
|
157
|
+
builds the distributions and runs everything against the installed wheel in a
|
|
158
|
+
clean environment:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
.\scripts\release-check.ps1
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Running headless
|
|
165
|
+
|
|
166
|
+
One process serves both halves: the routers mount under `/v1`, and the built web
|
|
167
|
+
bundle is mounted at `/`. To run the engine alone, behind somebody else's front
|
|
168
|
+
end, say so:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
uv run aggregate-api --headless # or AGGAPI_SERVE_SPA=0
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`/v1`, `/docs` and `/openapi.json` all stay; only `/` stops answering. A front
|
|
175
|
+
end on another origin also wants `AGGAPI_CORS_ORIGINS`, comma separated:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
AGGAPI_CORS_ORIGINS=https://your.app uv run aggregate-api --headless
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`/openapi.json` is the contract. Note that the `/v1` surface is pre-1.0 and its
|
|
182
|
+
shape follows what the bundled web app needs, so treat it as a moving target
|
|
183
|
+
rather than a stable interface for now.
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
BSD 3-Clause. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# aggregate_api
|
|
2
|
+
|
|
3
|
+
**aggregate Loss Lab** (aLL): a FastAPI service and a single-page web app for
|
|
4
|
+
the [`aggregate`](https://github.com/mynl/aggregate) actuarial library.
|
|
5
|
+
*Description to distribution.*
|
|
6
|
+
|
|
7
|
+
`aggregate_api` is the package; **aggregate Loss Lab** is the app it serves.
|
|
8
|
+
It puts `build()` behind an HTTP/JSON api (DecL parsing, FFT-based compound
|
|
9
|
+
distributions, plotting, and risk pricing) and ships a Bootstrap 5 and
|
|
10
|
+
CodeMirror 6 DecL workbench that runs against it. A single `aggregate-api`
|
|
11
|
+
process serves both the web UI (at `/`) and the JSON endpoints (under `/v1`)
|
|
12
|
+
same-origin.
|
|
13
|
+
|
|
14
|
+
> **Status:** 1.0.0, the first public release. Extracted from the `aggregate`
|
|
15
|
+
> repo so the library can ship without it. The package is installable and
|
|
16
|
+
> supported; the `/v1` surface is not yet a frozen interface, and the caveat at
|
|
17
|
+
> the end of [Running headless](#running-headless) says why. See
|
|
18
|
+
> [CHANGELOG.md](CHANGELOG.md) for what has landed and
|
|
19
|
+
> [dev/TODO.md](dev/TODO.md) for the roadmap.
|
|
20
|
+
|
|
21
|
+
## What's inside
|
|
22
|
+
|
|
23
|
+
- **Backend** (`src/aggregate_api/`): FastAPI app. Object lifecycle
|
|
24
|
+
(`POST /v1/objects` to build and cache, then `info`, `meta`, `summary`,
|
|
25
|
+
`tail_df`, `validation_df`, `stats_df`, `density_df`, `plot`, `kappa`,
|
|
26
|
+
`price`, `pricing_at`, plus `frame/{which}` in CSV or table-document form),
|
|
27
|
+
DecL helpers (`/v1/decl/complete`, `/lex`,
|
|
28
|
+
`/format`), and the example library read straight from `aggregate`'s recipe
|
|
29
|
+
base (`/v1/examples`, `/v1/examples/heroes`). In-memory LRU cache, build
|
|
30
|
+
timeout, SQLite audit log, CORS. Swagger UI at `/docs`.
|
|
31
|
+
- **Frontend** (`web/`): vanilla-JS SPA (Vite, Bootstrap, CodeMirror 6,
|
|
32
|
+
`csv-grid`). A DecL editor with syntax highlighting, autocomplete and history,
|
|
33
|
+
a landing gallery of showcase examples, tabbed risk output, interactive and
|
|
34
|
+
native plots, and a rich parse-error pane.
|
|
35
|
+
|
|
36
|
+
### Tables: one payload, two renderers
|
|
37
|
+
|
|
38
|
+
Every table under 500 rows is fetched once, as a **table document**: versioned
|
|
39
|
+
JSON carrying semantics only (dtypes, resolved formats, hierarchy, spans, flags)
|
|
40
|
+
and no widths or CSS. Both views render from that one document, so they cannot
|
|
41
|
+
disagree about a number:
|
|
42
|
+
|
|
43
|
+
- **Static** is `greater_tables`' own JS walker, which draws a book-quality table
|
|
44
|
+
(sparsified row index, spanned headers, partial rules). The walker and its
|
|
45
|
+
stylesheet are served straight out of the installed Python package at
|
|
46
|
+
`/v1/assets/`, so the renderer and the documents it renders can never version
|
|
47
|
+
skew.
|
|
48
|
+
- **Interactive** is `csv-grid`, fed by `irToGridInput(doc)`: sort, per-column
|
|
49
|
+
filter, fzf search, copy and save.
|
|
50
|
+
|
|
51
|
+
Which one you get is a page-wide preference in the header menu. Large frames (the
|
|
52
|
+
densities) skip the document entirely and go to the grid, which is the honest
|
|
53
|
+
instrument for them.
|
|
54
|
+
|
|
55
|
+
## Install
|
|
56
|
+
|
|
57
|
+
Requires Python ≥ 3.13. Nothing else: `aggregate` ≥ 1.0.1 and
|
|
58
|
+
`greater-tables` ≥ 6.0.0 come from PyPI with it.
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
pip install aggregate_api
|
|
62
|
+
aggregate-api --port 8001
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then open http://127.0.0.1:8001/ for the app, or http://127.0.0.1:8001/docs for
|
|
66
|
+
the api.
|
|
67
|
+
|
|
68
|
+
> **Install the wheel, not the repository.** The web bundle is 2.5 MB of
|
|
69
|
+
> generated Vite output and is deliberately not in git, so it reaches you in the
|
|
70
|
+
> built distribution and only there. `pip install git+https://github.com/mynl/aggregate_api`
|
|
71
|
+
> installs a working api whose `/` returns 404, which is a reasonable thing to
|
|
72
|
+
> want if you are putting your own front end in front of the engine, and a
|
|
73
|
+
> baffling one otherwise. The wheel and sdist are also attached to each
|
|
74
|
+
> [GitHub release](https://github.com/mynl/aggregate_api/releases).
|
|
75
|
+
|
|
76
|
+
One footnote, which the pins above handle for you and which bites anyone
|
|
77
|
+
installing the table engine on its own: PyPI's `greater-tables` 5.3 is a
|
|
78
|
+
previous generation sharing the `greater_tables` import name, so a bare
|
|
79
|
+
`pip install greater-tables` can get a package with no `build`, `canonical_json`
|
|
80
|
+
or `IR_VERSION`, which fails at import rather than at install. This package pins
|
|
81
|
+
`>=6.0.0` and `tests/test_meta.py` asserts the right generation is present.
|
|
82
|
+
|
|
83
|
+
## Develop
|
|
84
|
+
|
|
85
|
+
Needs [`uv`](https://docs.astral.sh/uv/) and Node for the web build. A fresh
|
|
86
|
+
clone syncs with nothing checked out beside it.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
git clone https://github.com/mynl/aggregate_api
|
|
90
|
+
cd aggregate_api
|
|
91
|
+
uv sync --extra dev
|
|
92
|
+
.\scripts\build-web.ps1 # Windows; ./scripts/build-web.sh on Unix
|
|
93
|
+
uv run aggregate-api --port 8001 --reload
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`build-web` drops the bundle into `src/aggregate_api/static/`, which the FastAPI
|
|
97
|
+
`StaticFiles` mount serves at `/`. Until it has run once, `/` has nothing to
|
|
98
|
+
serve. For hot reload with a Vite dev server proxying `/v1` to `:8000`, use
|
|
99
|
+
`cd web; npm install; npm run dev` instead.
|
|
100
|
+
|
|
101
|
+
To develop against a local checkout of `aggregate` or `greater-tables` rather
|
|
102
|
+
than the released build, install it editable over the synced environment:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
uv pip install -e ../aggregate # or wherever the checkout is
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The next `uv sync` silently reinstalls the released build, so `uv run --no-sync`
|
|
109
|
+
is the way to work in between.
|
|
110
|
+
|
|
111
|
+
Run the tests. There are two suites and both are the gate:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
uv run pytest # 490 tests, FastAPI TestClient, no live server needed
|
|
115
|
+
cd web; npm test # 277 tests, node --test over web/test/
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Rehearse a release, which is a stronger check than either suite because it
|
|
119
|
+
builds the distributions and runs everything against the installed wheel in a
|
|
120
|
+
clean environment:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
.\scripts\release-check.ps1
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Running headless
|
|
127
|
+
|
|
128
|
+
One process serves both halves: the routers mount under `/v1`, and the built web
|
|
129
|
+
bundle is mounted at `/`. To run the engine alone, behind somebody else's front
|
|
130
|
+
end, say so:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
uv run aggregate-api --headless # or AGGAPI_SERVE_SPA=0
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`/v1`, `/docs` and `/openapi.json` all stay; only `/` stops answering. A front
|
|
137
|
+
end on another origin also wants `AGGAPI_CORS_ORIGINS`, comma separated:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
AGGAPI_CORS_ORIGINS=https://your.app uv run aggregate-api --headless
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`/openapi.json` is the contract. Note that the `/v1` surface is pre-1.0 and its
|
|
144
|
+
shape follows what the bundled web app needs, so treat it as a moving target
|
|
145
|
+
rather than a stable interface for now.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
BSD 3-Clause. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=70", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aggregate_api"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "aggregate Loss Lab (aLL): FastAPI service and single-page web app for the aggregate actuarial library."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{name = "Stephen J. Mildenhall", email = "steve@convexrisk.com"},
|
|
12
|
+
]
|
|
13
|
+
maintainers = [
|
|
14
|
+
{name = "Stephen J. Mildenhall", email = "steve@convexrisk.com"},
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"actuarial",
|
|
18
|
+
"insurance",
|
|
19
|
+
"reinsurance",
|
|
20
|
+
"risk",
|
|
21
|
+
"aggregate loss",
|
|
22
|
+
"compound distribution",
|
|
23
|
+
"FFT",
|
|
24
|
+
"pricing",
|
|
25
|
+
"DecL",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
# Beta rather than Production/Stable at 1.0.0, deliberately. The package is
|
|
29
|
+
# installable and supported; the `/v1` surface still follows what the
|
|
30
|
+
# bundled web app needs, so it is not a frozen interface. The library can
|
|
31
|
+
# claim Production/Stable because its API promise is in force; this cannot
|
|
32
|
+
# yet.
|
|
33
|
+
"Development Status :: 4 - Beta",
|
|
34
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
35
|
+
"Programming Language :: Python :: 3",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Programming Language :: Python :: 3.14",
|
|
38
|
+
"Topic :: Office/Business :: Financial",
|
|
39
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
40
|
+
"Framework :: FastAPI",
|
|
41
|
+
]
|
|
42
|
+
dependencies = [
|
|
43
|
+
# The library this service wraps. An ordinary registry dependency since
|
|
44
|
+
# 1.0.0 of this package: `aggregate` 1.0.1 published on 2026-10-07 and the
|
|
45
|
+
# internals the api imports (`parser_errors`, `parser._PARSER`, `charts`,
|
|
46
|
+
# `exhibits`, `plugins`, `bounds`, `decl_writer`, `constants`) are all in
|
|
47
|
+
# it. Before that the whole 1.0.0a line was unpublished and this had to be
|
|
48
|
+
# an editable path source; see dev/done/plan-1.0.0-release.md.
|
|
49
|
+
#
|
|
50
|
+
# The floor is 1.0.1 and not 1.0.0 because 1.0.0 is not on PyPI at all:
|
|
51
|
+
# the library's own 1.0.1 was a packaging fix, "the wheel ships
|
|
52
|
+
# aggregate.charts, .plots and .exhibits", and the api imports two of those
|
|
53
|
+
# three. A bare `aggregate` would also let a resolver reach 0.30.1, the
|
|
54
|
+
# newest of the old line, which has none of these modules and fails at
|
|
55
|
+
# import rather than at install.
|
|
56
|
+
#
|
|
57
|
+
# The ceiling is caution about two private names, `parser._PARSER` and
|
|
58
|
+
# `parser_errors._TERMINAL_LABELS`. The library's API promise covers its
|
|
59
|
+
# public surface, and a major is exactly where a private name moves.
|
|
60
|
+
# Re-check both at each library minor rather than trusting the range.
|
|
61
|
+
"aggregate>=1.0.1,<2",
|
|
62
|
+
# Static tables (the author's own package). The api asks it for a semantic
|
|
63
|
+
# table document (the IR), never for markup: the browser owns geometry. The
|
|
64
|
+
# same install also ships the walker that renders that IR, which is what
|
|
65
|
+
# makes version skew between the two impossible. See dev/plan-gt2-ir.md.
|
|
66
|
+
#
|
|
67
|
+
# **An ordinary registry dependency since a53.** 6.0.0 has shipped to PyPI,
|
|
68
|
+
# so the editable source this used to carry is gone and a fresh clone works
|
|
69
|
+
# with nothing checked out beside it.
|
|
70
|
+
#
|
|
71
|
+
# The floor is load bearing and must not be relaxed. PyPI also holds 5.3,
|
|
72
|
+
# the previous generation of the same package under the same name, and the
|
|
73
|
+
# two cannot coexist in one environment: 5.3 has no `build`,
|
|
74
|
+
# `canonical_json` or `IR_VERSION`, so resolving to it does not fail at
|
|
75
|
+
# install, it fails at import. Without `>=6` a resolver working around some
|
|
76
|
+
# other constraint could quietly pick it.
|
|
77
|
+
"greater-tables>=6.0.0",
|
|
78
|
+
"fastapi>=0.115",
|
|
79
|
+
"uvicorn[standard]>=0.30",
|
|
80
|
+
"pydantic>=2.7",
|
|
81
|
+
"pydantic-settings>=2.4",
|
|
82
|
+
]
|
|
83
|
+
license = "BSD-3-Clause"
|
|
84
|
+
# Was >=3.11 through a31. `greater-tables` declares >=3.13 and that floor is
|
|
85
|
+
# the binding one. Nothing in its source needs 3.13, so if 3.11 or 3.12 ever
|
|
86
|
+
# matters here again, relax it there rather than pinning an old release here.
|
|
87
|
+
requires-python = ">=3.13"
|
|
88
|
+
|
|
89
|
+
[project.urls]
|
|
90
|
+
Homepage = "https://github.com/mynl/aggregate_api"
|
|
91
|
+
"Source Code" = "https://github.com/mynl/aggregate_api"
|
|
92
|
+
Documentation = "https://github.com/mynl/aggregate_api#readme"
|
|
93
|
+
Changelog = "https://github.com/mynl/aggregate_api/blob/main/CHANGELOG.md"
|
|
94
|
+
Issues = "https://github.com/mynl/aggregate_api/issues"
|
|
95
|
+
|
|
96
|
+
[project.optional-dependencies]
|
|
97
|
+
dev = [
|
|
98
|
+
"pytest>=7",
|
|
99
|
+
# FastAPI's TestClient is a thin wrapper over httpx.
|
|
100
|
+
"httpx>=0.27",
|
|
101
|
+
"ruff>=0.6",
|
|
102
|
+
]
|
|
103
|
+
# The resource block of GET /v1/status. An extra rather than a runtime
|
|
104
|
+
# dependency so the lean install is untouched and a deployment opts in with
|
|
105
|
+
# `uv sync --extra status`; `resources.py` carries a stdlib fallback and says
|
|
106
|
+
# which source it used, so the page works either way.
|
|
107
|
+
#
|
|
108
|
+
# Worth declaring even though psutil is usually already present: it arrives
|
|
109
|
+
# transitively through ipython, which aggregate pulls, so the import succeeds
|
|
110
|
+
# by accident rather than by intent and is one unrelated dependency change away
|
|
111
|
+
# from not. That is what an explicit extra is for.
|
|
112
|
+
status = ["psutil>=5.9"]
|
|
113
|
+
|
|
114
|
+
[project.scripts]
|
|
115
|
+
aggregate-api = "aggregate_api.__main__:main"
|
|
116
|
+
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
# There is deliberately no `[tool.uv.sources]` table.
|
|
119
|
+
#
|
|
120
|
+
# It held an editable path source for `aggregate` through 1.0.0a202, because the
|
|
121
|
+
# whole 1.0.0a line was unpublished, and one for `greater-tables` through a53.
|
|
122
|
+
# Both dependencies are on PyPI now, so a fresh clone syncs with nothing checked
|
|
123
|
+
# out beside it.
|
|
124
|
+
#
|
|
125
|
+
# The table has to go rather than merely being unused, because uv honors it when
|
|
126
|
+
# this project is installed *as a git dependency* and resolves the relative path
|
|
127
|
+
# inside the git repository:
|
|
128
|
+
#
|
|
129
|
+
# uv pip install git+https://github.com/mynl/aggregate_api
|
|
130
|
+
# x Failed to download and build `aggregate @ git+...#subdirectory=..\..\worktrees\aggregate_REFACTOR`
|
|
131
|
+
# The source distribution has no subdirectory `..\..\worktrees\aggregate_REFACTOR`
|
|
132
|
+
#
|
|
133
|
+
# So the table did not just fail to help an outside installer, it was fatal to
|
|
134
|
+
# one. pip never saw it, which is why the failure was uv-only and invisible here.
|
|
135
|
+
#
|
|
136
|
+
# To co-develop either dependency against a local checkout for a session,
|
|
137
|
+
# without editing this file:
|
|
138
|
+
#
|
|
139
|
+
# uv sync --extra dev # PyPI, the default
|
|
140
|
+
# uv pip install -e V:/worktrees/aggregate_REFACTOR # swap in the library
|
|
141
|
+
# uv pip install -e c:/s/ai/greatest-tables # swap in the tables
|
|
142
|
+
#
|
|
143
|
+
# The tables checkout folder is still named `greatest-tables` after the working
|
|
144
|
+
# name it carried for a day; that is deliberate and the path above is correct.
|
|
145
|
+
#
|
|
146
|
+
# Either swap lasts until the next `uv sync`, which reinstalls from the lock and
|
|
147
|
+
# silently puts the released build back without saying so. That matters because
|
|
148
|
+
# the release workflow runs `uv sync` on every version bump, so a bump in the
|
|
149
|
+
# middle of co-development drops the editable install. `uv run --no-sync` is the
|
|
150
|
+
# way to work in between, and it is what this repo uses anyway.
|
|
151
|
+
# ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
[tool.setuptools]
|
|
154
|
+
include-package-data = true
|
|
155
|
+
package-dir = {"" = "src"}
|
|
156
|
+
packages = ["aggregate_api", "aggregate_api.routes"]
|
|
157
|
+
|
|
158
|
+
[tool.setuptools.package-data]
|
|
159
|
+
# The built SPA bundle (index.html + assets/) is dropped here by the web
|
|
160
|
+
# build and is gitignored; the committed icons/webmanifest ship too.
|
|
161
|
+
#
|
|
162
|
+
# **`static/assets/*` is a separate entry and must stay one.** A package_data
|
|
163
|
+
# glob matches files in one directory and does not recurse, so `static/*` alone
|
|
164
|
+
# shipped `index.html` and left every script and stylesheet it loads behind.
|
|
165
|
+
# The installed app then served a 200 for `/` and a 404 for all thirteen
|
|
166
|
+
# assets: the shell painted and nothing ran. `static/**/*` would also work, and
|
|
167
|
+
# is not used here because it would sweep in `static/dev/`, which holds
|
|
168
|
+
# development instruments that have no business in a release.
|
|
169
|
+
#
|
|
170
|
+
# `status_page.html` is the operator's page, served by routes/status.py straight
|
|
171
|
+
# out of the package. Deliberately not under `static/`: the web build wipes that
|
|
172
|
+
# directory on every run, so a copy there would be deleted by the next deploy,
|
|
173
|
+
# and a status page whose delivery depends on the pipeline it reports on cannot
|
|
174
|
+
# report on that pipeline failing.
|
|
175
|
+
aggregate_api = ["static/*", "static/assets/*", "status_page.html"]
|
|
176
|
+
|
|
177
|
+
[tool.ruff]
|
|
178
|
+
line-length = 100
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""FastAPI service for the :mod:`aggregate` library.
|
|
2
|
+
|
|
3
|
+
This package stands up an HTTP/JSON wrapper around ``build()``,
|
|
4
|
+
the live ``Aggregate`` / ``Portfolio`` objects, plotting, pricing,
|
|
5
|
+
and DecL helpers (completions, lexing, grammar).
|
|
6
|
+
|
|
7
|
+
Quickstart
|
|
8
|
+
----------
|
|
9
|
+
|
|
10
|
+
::
|
|
11
|
+
|
|
12
|
+
pip install aggregate_api
|
|
13
|
+
aggregate-api --port 8001
|
|
14
|
+
|
|
15
|
+
Then ``POST http://127.0.0.1:8001/v1/objects`` with body
|
|
16
|
+
``{"decl": "agg Dice dfreq [3] dsev [1:6]"}`` to build and cache an
|
|
17
|
+
object, and follow up with ``GET /v1/objects/{id}/info`` etc.
|
|
18
|
+
|
|
19
|
+
Public surface
|
|
20
|
+
--------------
|
|
21
|
+
|
|
22
|
+
``create_app()`` returns a configured :class:`fastapi.FastAPI`
|
|
23
|
+
instance. The ``aggregate-api`` console script launches it under
|
|
24
|
+
uvicorn. Tests build their own ``TestClient`` against
|
|
25
|
+
``create_app()``.
|
|
26
|
+
|
|
27
|
+
Note for Flask users
|
|
28
|
+
--------------------
|
|
29
|
+
|
|
30
|
+
FastAPI uses an *application factory* (``create_app``) plus an ASGI
|
|
31
|
+
server (uvicorn) instead of Flask's ``app = Flask(__name__)`` + WSGI.
|
|
32
|
+
Routes are grouped on ``APIRouter`` objects (analogous to Flask
|
|
33
|
+
``Blueprint``) and mounted onto the app in :func:`app.create_app`.
|
|
34
|
+
Request bodies are *validated* by Pydantic models (one model per
|
|
35
|
+
endpoint) rather than read out of ``request.json``; the model is a
|
|
36
|
+
function parameter and FastAPI deserializes for you.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from .app import create_app
|
|
40
|
+
|
|
41
|
+
__all__ = ["create_app"]
|