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.
Files changed (90) hide show
  1. aggregate_api-1.0.0/LICENSE +28 -0
  2. aggregate_api-1.0.0/PKG-INFO +187 -0
  3. aggregate_api-1.0.0/README.md +149 -0
  4. aggregate_api-1.0.0/pyproject.toml +178 -0
  5. aggregate_api-1.0.0/setup.cfg +4 -0
  6. aggregate_api-1.0.0/src/aggregate_api/__init__.py +41 -0
  7. aggregate_api-1.0.0/src/aggregate_api/__main__.py +154 -0
  8. aggregate_api-1.0.0/src/aggregate_api/app.py +206 -0
  9. aggregate_api-1.0.0/src/aggregate_api/audit.py +395 -0
  10. aggregate_api-1.0.0/src/aggregate_api/bounds.py +331 -0
  11. aggregate_api-1.0.0/src/aggregate_api/cache.py +319 -0
  12. aggregate_api-1.0.0/src/aggregate_api/capability.py +823 -0
  13. aggregate_api-1.0.0/src/aggregate_api/completion.py +219 -0
  14. aggregate_api-1.0.0/src/aggregate_api/config.py +363 -0
  15. aggregate_api-1.0.0/src/aggregate_api/cors.py +61 -0
  16. aggregate_api-1.0.0/src/aggregate_api/examples.py +620 -0
  17. aggregate_api-1.0.0/src/aggregate_api/layer_pricing.py +840 -0
  18. aggregate_api-1.0.0/src/aggregate_api/library.py +94 -0
  19. aggregate_api-1.0.0/src/aggregate_api/library_notes.py +96 -0
  20. aggregate_api-1.0.0/src/aggregate_api/models.py +1407 -0
  21. aggregate_api-1.0.0/src/aggregate_api/net.py +281 -0
  22. aggregate_api-1.0.0/src/aggregate_api/pnl.py +101 -0
  23. aggregate_api-1.0.0/src/aggregate_api/pricing.py +778 -0
  24. aggregate_api-1.0.0/src/aggregate_api/resources.py +257 -0
  25. aggregate_api-1.0.0/src/aggregate_api/routes/__init__.py +8 -0
  26. aggregate_api-1.0.0/src/aggregate_api/routes/decl.py +327 -0
  27. aggregate_api-1.0.0/src/aggregate_api/routes/examples.py +82 -0
  28. aggregate_api-1.0.0/src/aggregate_api/routes/meta.py +282 -0
  29. aggregate_api-1.0.0/src/aggregate_api/routes/objects.py +4119 -0
  30. aggregate_api-1.0.0/src/aggregate_api/routes/status.py +466 -0
  31. aggregate_api-1.0.0/src/aggregate_api/serializers.py +565 -0
  32. aggregate_api-1.0.0/src/aggregate_api/sessions.py +353 -0
  33. aggregate_api-1.0.0/src/aggregate_api/static/aggregate-api-logo-512.png +0 -0
  34. aggregate_api-1.0.0/src/aggregate_api/static/aggregate-api-logo.png +0 -0
  35. aggregate_api-1.0.0/src/aggregate_api/static/aggregate-api-trim.png +0 -0
  36. aggregate_api-1.0.0/src/aggregate_api/static/android-chrome-192x192.png +0 -0
  37. aggregate_api-1.0.0/src/aggregate_api/static/android-chrome-512x512.png +0 -0
  38. aggregate_api-1.0.0/src/aggregate_api/static/apple-touch-icon.png +0 -0
  39. aggregate_api-1.0.0/src/aggregate_api/static/assets/bootstrap-icons-BeopsB42.woff +0 -0
  40. aggregate_api-1.0.0/src/aggregate_api/static/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
  41. aggregate_api-1.0.0/src/aggregate_api/static/assets/bootstrap-ohb1VZ53.js +5 -0
  42. aggregate_api-1.0.0/src/aggregate_api/static/assets/codemirror-h62DHGGa.js +14 -0
  43. aggregate_api-1.0.0/src/aggregate_api/static/assets/csv-grid.worker-DKzHGXac.js +4 -0
  44. aggregate_api-1.0.0/src/aggregate_api/static/assets/echarts-B7o9sc00.js +40 -0
  45. aggregate_api-1.0.0/src/aggregate_api/static/assets/echarts-gl-DG1Uf6wE.js +4282 -0
  46. aggregate_api-1.0.0/src/aggregate_api/static/assets/lite-CUlcD8p4.css +1 -0
  47. aggregate_api-1.0.0/src/aggregate_api/static/assets/lite-Dd2TnT4M.js +1 -0
  48. aggregate_api-1.0.0/src/aggregate_api/static/assets/main-Bxhxa55v.css +9 -0
  49. aggregate_api-1.0.0/src/aggregate_api/static/assets/main-CmoEiPit.js +9 -0
  50. aggregate_api-1.0.0/src/aggregate_api/static/assets/tables-BHCF7qIF.js +8 -0
  51. aggregate_api-1.0.0/src/aggregate_api/static/assets/tables-CxvajLr7.css +1 -0
  52. aggregate_api-1.0.0/src/aggregate_api/static/favicon-16x16.png +0 -0
  53. aggregate_api-1.0.0/src/aggregate_api/static/favicon-32x32.png +0 -0
  54. aggregate_api-1.0.0/src/aggregate_api/static/favicon.ico +0 -0
  55. aggregate_api-1.0.0/src/aggregate_api/static/index.html +912 -0
  56. aggregate_api-1.0.0/src/aggregate_api/static/lite.html +83 -0
  57. aggregate_api-1.0.0/src/aggregate_api/static/logo.png +0 -0
  58. aggregate_api-1.0.0/src/aggregate_api/static/site.webmanifest +14 -0
  59. aggregate_api-1.0.0/src/aggregate_api/static/sw.js +78 -0
  60. aggregate_api-1.0.0/src/aggregate_api/status.py +536 -0
  61. aggregate_api-1.0.0/src/aggregate_api/status_page.html +546 -0
  62. aggregate_api-1.0.0/src/aggregate_api/tables.py +316 -0
  63. aggregate_api-1.0.0/src/aggregate_api.egg-info/PKG-INFO +187 -0
  64. aggregate_api-1.0.0/src/aggregate_api.egg-info/SOURCES.txt +88 -0
  65. aggregate_api-1.0.0/src/aggregate_api.egg-info/dependency_links.txt +1 -0
  66. aggregate_api-1.0.0/src/aggregate_api.egg-info/entry_points.txt +2 -0
  67. aggregate_api-1.0.0/src/aggregate_api.egg-info/requires.txt +14 -0
  68. aggregate_api-1.0.0/src/aggregate_api.egg-info/top_level.txt +1 -0
  69. aggregate_api-1.0.0/tests/test_audit.py +41 -0
  70. aggregate_api-1.0.0/tests/test_bounds.py +470 -0
  71. aggregate_api-1.0.0/tests/test_capability.py +515 -0
  72. aggregate_api-1.0.0/tests/test_cli.py +45 -0
  73. aggregate_api-1.0.0/tests/test_cors.py +44 -0
  74. aggregate_api-1.0.0/tests/test_decl.py +102 -0
  75. aggregate_api-1.0.0/tests/test_decl_parse.py +250 -0
  76. aggregate_api-1.0.0/tests/test_derive.py +753 -0
  77. aggregate_api-1.0.0/tests/test_entry_lock.py +160 -0
  78. aggregate_api-1.0.0/tests/test_examples.py +349 -0
  79. aggregate_api-1.0.0/tests/test_headless.py +75 -0
  80. aggregate_api-1.0.0/tests/test_layer_pricing.py +528 -0
  81. aggregate_api-1.0.0/tests/test_library.py +159 -0
  82. aggregate_api-1.0.0/tests/test_meta.py +137 -0
  83. aggregate_api-1.0.0/tests/test_objects.py +2363 -0
  84. aggregate_api-1.0.0/tests/test_packaging.py +84 -0
  85. aggregate_api-1.0.0/tests/test_plugins_meta.py +187 -0
  86. aggregate_api-1.0.0/tests/test_pnl_pentagon_route.py +181 -0
  87. aggregate_api-1.0.0/tests/test_pricing_exhibits.py +853 -0
  88. aggregate_api-1.0.0/tests/test_ruin_route.py +130 -0
  89. aggregate_api-1.0.0/tests/test_sessions.py +600 -0
  90. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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"]