assay-engine 0.5.0.dev2__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.
- assay_engine-0.5.0.dev2/LICENSE +21 -0
- assay_engine-0.5.0.dev2/PKG-INFO +250 -0
- assay_engine-0.5.0.dev2/README.md +222 -0
- assay_engine-0.5.0.dev2/pyproject.toml +173 -0
- assay_engine-0.5.0.dev2/src/assay/__init__.py +83 -0
- assay_engine-0.5.0.dev2/src/assay/_cli_app.py +102 -0
- assay_engine-0.5.0.dev2/src/assay/_cli_io.py +130 -0
- assay_engine-0.5.0.dev2/src/assay/_json.py +34 -0
- assay_engine-0.5.0.dev2/src/assay/_optional.py +69 -0
- assay_engine-0.5.0.dev2/src/assay/_version.py +5 -0
- assay_engine-0.5.0.dev2/src/assay/additive.py +104 -0
- assay_engine-0.5.0.dev2/src/assay/agreement.py +312 -0
- assay_engine-0.5.0.dev2/src/assay/calibration.py +167 -0
- assay_engine-0.5.0.dev2/src/assay/cli.py +55 -0
- assay_engine-0.5.0.dev2/src/assay/compose.py +31 -0
- assay_engine-0.5.0.dev2/src/assay/composite.py +228 -0
- assay_engine-0.5.0.dev2/src/assay/contracts.py +886 -0
- assay_engine-0.5.0.dev2/src/assay/errors.py +166 -0
- assay_engine-0.5.0.dev2/src/assay/limits.py +13 -0
- assay_engine-0.5.0.dev2/src/assay/measurement.py +1325 -0
- assay_engine-0.5.0.dev2/src/assay/metrics.py +238 -0
- assay_engine-0.5.0.dev2/src/assay/minimum.py +82 -0
- assay_engine-0.5.0.dev2/src/assay/models.py +111 -0
- assay_engine-0.5.0.dev2/src/assay/normalize.py +74 -0
- assay_engine-0.5.0.dev2/src/assay/py.typed +0 -0
- assay_engine-0.5.0.dev2/src/assay/ranking.py +361 -0
- assay_engine-0.5.0.dev2/src/assay/settings.py +90 -0
- assay_engine-0.5.0.dev2/src/assay/uncertainty.py +190 -0
- assay_engine-0.5.0.dev2/src/assay/weighted_mean.py +109 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harish Seshadri
|
|
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,250 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: assay-engine
|
|
3
|
+
Version: 0.5.0.dev2
|
|
4
|
+
Summary: Explainable composition of measurements recorded on heterogeneous scales.
|
|
5
|
+
Project-URL: Homepage, https://github.com/hseshadr/assay
|
|
6
|
+
Project-URL: Repository, https://github.com/hseshadr/assay
|
|
7
|
+
Project-URL: Issues, https://github.com/hseshadr/assay/issues
|
|
8
|
+
Author-email: Harish Seshadri <harish.seshadri@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Requires-Dist: pydantic>=2.11
|
|
19
|
+
Provides-Extra: cli
|
|
20
|
+
Requires-Dist: typer>=0.27; extra == 'cli'
|
|
21
|
+
Provides-Extra: metrics
|
|
22
|
+
Requires-Dist: ir-measures>=0.4.3; extra == 'metrics'
|
|
23
|
+
Requires-Dist: numpy>=2.5; extra == 'metrics'
|
|
24
|
+
Requires-Dist: pydantic-settings>=2.11; extra == 'metrics'
|
|
25
|
+
Requires-Dist: scikit-learn>=1.9; extra == 'metrics'
|
|
26
|
+
Requires-Dist: scipy>=1.18; extra == 'metrics'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# Assay
|
|
30
|
+
|
|
31
|
+
> **TL;DR:** Assay combines measurements recorded on different scales into one explainable score while preserving every input, transformation, and contribution.
|
|
32
|
+
|
|
33
|
+
Assay is a small scoring engine for formulas you can write down and replay. Give it
|
|
34
|
+
measurements, their native scales, and one explicit combining method. It returns the
|
|
35
|
+
score and the arithmetic behind every row.
|
|
36
|
+
|
|
37
|
+
## Installation status
|
|
38
|
+
|
|
39
|
+
> **Status:** `assay-engine` 0.5.0.dev2 and `@edgeproc/assay` 0.5.0-dev.2 are local split candidates. Neither package is published.
|
|
40
|
+
|
|
41
|
+
The future authorized registry commands are `pip install assay-engine` and
|
|
42
|
+
`npm install @edgeproc/assay`. They are shown for identity only; do not run them until
|
|
43
|
+
a release is explicitly authorized. The runnable candidate path builds from this
|
|
44
|
+
checkout.
|
|
45
|
+
|
|
46
|
+
## Run the Northstar example
|
|
47
|
+
|
|
48
|
+
From the checkout root, run:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
bash examples/run_composite.sh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The script builds the real Python wheel and npm tarball, installs each in an isolated
|
|
55
|
+
temporary environment, computes through both public package surfaces, checks every
|
|
56
|
+
typed field and binary64 value against the committed oracle, and prints one explanation:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
Northstar weighted score: 0.92
|
|
60
|
+
Method: weighted_mean @ northstar.2026-08-12
|
|
61
|
+
Interval: null — all inputs are deterministic
|
|
62
|
+
|
|
63
|
+
security 19/20 -> 0.950000 × 0.20 = 0.19
|
|
64
|
+
privacy 15/15 -> 1.000000 × 0.15 = 0.15
|
|
65
|
+
reliability 15/15 -> 1.000000 × 0.15 = 0.15
|
|
66
|
+
performance 12/15 -> 0.800000 × 0.15 = 0.12
|
|
67
|
+
correctness 15/15 -> 1.000000 × 0.15 = 0.15
|
|
68
|
+
clarity 14/15 -> 0.933333 × 0.15 = 0.14
|
|
69
|
+
production 2/5 -> 0.400000 × 0.05 = 0.02
|
|
70
|
+
|
|
71
|
+
Total: 0.92
|
|
72
|
+
inputs_hash: sha256:0266b1c59c97bacf85dc945685c55bb4386856b525249c7d5663a8edf020ba06
|
|
73
|
+
Parity: Python and TypeScript fields and values match
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This is uncapped arithmetic only. Northstar hard caps, evidence grades, release
|
|
77
|
+
decisions, and other product policies remain outside Assay.
|
|
78
|
+
|
|
79
|
+
## How the score is calculated
|
|
80
|
+
|
|
81
|
+
The example declares seven components on three native scales. Assay first normalizes
|
|
82
|
+
each value to 0–1, divides its positive weight by the declared total of 100, then adds
|
|
83
|
+
the contributions in declaration order:
|
|
84
|
+
|
|
85
|
+
```text
|
|
86
|
+
security: (19 - 0) / (20 - 0) × 20/100 = 0.19
|
|
87
|
+
privacy: (15 - 0) / (15 - 0) × 15/100 = 0.15
|
|
88
|
+
reliability: (15 - 0) / (15 - 0) × 15/100 = 0.15
|
|
89
|
+
performance: (12 - 0) / (15 - 0) × 15/100 = 0.12
|
|
90
|
+
correctness: (15 - 0) / (15 - 0) × 15/100 = 0.15
|
|
91
|
+
clarity: (14 - 0) / (15 - 0) × 15/100 = 0.14
|
|
92
|
+
production: ( 2 - 0) / ( 5 - 0) × 5/100 = 0.02
|
|
93
|
+
total: 0.92
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Assay's portable typed API supports exactly three composition methods:
|
|
97
|
+
|
|
98
|
+
- `weighted_mean` normalizes components, converts positive declared weights into
|
|
99
|
+
coefficients that sum to one, and adds their contributions.
|
|
100
|
+
- `additive` applies each raw term's explicit add or subtract operation and coefficient,
|
|
101
|
+
then optionally clamps the final total.
|
|
102
|
+
- `minimum` normalizes components and selects the first lowest value, making declaration
|
|
103
|
+
order the tie-breaker.
|
|
104
|
+
|
|
105
|
+
The method is chosen by the application because it owns the formula. Assay never
|
|
106
|
+
silently replaces a shipped formula with an average. See
|
|
107
|
+
[Methods](https://github.com/hseshadr/assay/blob/main/docs/METHODS.md) for validation,
|
|
108
|
+
uncertainty, and exact arithmetic rules.
|
|
109
|
+
|
|
110
|
+
### Legacy Python compatibility
|
|
111
|
+
|
|
112
|
+
The wheel retains a Python-only migration adapter at the deep import `assay.composite`:
|
|
113
|
+
`SubScore` plus `composite(...)`. It is not exported from the package root, does not
|
|
114
|
+
return the typed method or `inputs_hash` fields, and has no TypeScript equivalent. For
|
|
115
|
+
all new code, use package-root `parse_request()` and `compose()` with one of the three
|
|
116
|
+
portable methods above.
|
|
117
|
+
|
|
118
|
+
Every result field is explicit:
|
|
119
|
+
|
|
120
|
+
| Field | Meaning |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `schema` | Serialized result contract, currently `assay.result/v1`. |
|
|
123
|
+
| `method.id` | One of the three portable typed composition methods. |
|
|
124
|
+
| `method.version` | Caller-declared provenance for this formula revision. |
|
|
125
|
+
| `score` | Final finite binary64 result. |
|
|
126
|
+
| `interval` | Propagated uncertainty bounds, or `null` for deterministic inputs. |
|
|
127
|
+
| `clamp` | Requested boundary policy, or `null` only for unclamped additive scoring. |
|
|
128
|
+
| `intercept` | Additive starting value; `null` for the other methods. |
|
|
129
|
+
| `weight_total` | Weighted-mean declared weight total; otherwise `null`. |
|
|
130
|
+
| `components` | Ordered arithmetic rows retained for replay. |
|
|
131
|
+
| `id` | Stable input identifier for one row. |
|
|
132
|
+
| `raw` | Original finite input value; it may be sensitive. |
|
|
133
|
+
| `normalized` | 0–1 transformed value, or `null` for additive rows. |
|
|
134
|
+
| `declared_weight` | Original weighted-mean weight, otherwise `null`. |
|
|
135
|
+
| `operation` | `add` or `subtract`; normalized methods use `add`. |
|
|
136
|
+
| `coefficient` | Effective multiplier used for the row. |
|
|
137
|
+
| `contribution` | Pre-operation product: `normalized × coefficient` or `raw × coefficient`. For additive rows, `operation` controls how it changes the running total. |
|
|
138
|
+
| `contribution_interval` | Row uncertainty contribution, or `null`. |
|
|
139
|
+
| `inputs_hash` | Order-preserving request fingerprint used for replay comparison. |
|
|
140
|
+
| `selected_component_id` | Minimum-method bottleneck ID; otherwise `null`. |
|
|
141
|
+
|
|
142
|
+
Python and TypeScript parity covers the three methods, typed field/value structure,
|
|
143
|
+
field and component order, IEEE-754 binary64 values, and the exact `inputs_hash`. It
|
|
144
|
+
does not promise byte-identical output from language-native JSON serializers; for
|
|
145
|
+
example, one serializer may spell the same number `19.0` and another `19`.
|
|
146
|
+
|
|
147
|
+
## What this proves
|
|
148
|
+
|
|
149
|
+
For a validated request, the result exposes the selected method and version, preserves
|
|
150
|
+
the scored inputs in declaration order, shows every transformation and contribution,
|
|
151
|
+
and can be replayed under the same contract. The committed vectors prove the Python and
|
|
152
|
+
TypeScript composition surfaces agree semantically on all three methods and on the
|
|
153
|
+
exact request fingerprint.
|
|
154
|
+
|
|
155
|
+
## What this does not prove
|
|
156
|
+
|
|
157
|
+
Assay does not prove input truth, completeness, fairness, freshness, authenticity,
|
|
158
|
+
policy compliance, or decision quality. `inputs_hash` is a deterministic fingerprint,
|
|
159
|
+
not authentication or tamper evidence. A caller-declared method version records
|
|
160
|
+
provenance; it does not validate the methodology.
|
|
161
|
+
|
|
162
|
+
Application-owned bands, thresholds, hard gates, fairness review, abstention policy,
|
|
163
|
+
release decisions, and other downstream decisions remain application-owned. Results
|
|
164
|
+
retain raw values, so callers must treat them according to the sensitivity of their
|
|
165
|
+
inputs.
|
|
166
|
+
|
|
167
|
+
## Architecture
|
|
168
|
+
|
|
169
|
+
There are exactly two production source-to-artifact mappings:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
src/assay/ ──> assay-engine wheel ──> import assay
|
|
173
|
+
ts/src/ ──> @edgeproc/assay npm tarball ──> import "@edgeproc/assay"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`examples/`, `docs/`, `tests/`, and `testdata/` are repository support files, not
|
|
177
|
+
runtime packages. The Python package is the broader surface: composition is in the
|
|
178
|
+
base wheel, the command line uses the `cli` extra, and scientific calculators use the
|
|
179
|
+
`metrics` extra. The npm tarball provides composition plus a smaller set of optional
|
|
180
|
+
binary and ranking calculators.
|
|
181
|
+
|
|
182
|
+
This README is self-contained because the Python source distribution currently ships
|
|
183
|
+
it, but does not ship the repository's quickstart, docs, or examples. The detailed
|
|
184
|
+
[architecture](https://github.com/hseshadr/assay/blob/main/docs/ARCHITECTURE.md),
|
|
185
|
+
[operations contract](https://github.com/hseshadr/assay/blob/main/docs/OPERATIONS.md),
|
|
186
|
+
and [quickstart](https://github.com/hseshadr/assay/blob/main/QUICKSTART.md) are available
|
|
187
|
+
in the source checkout.
|
|
188
|
+
|
|
189
|
+
## Use the local candidate directly
|
|
190
|
+
|
|
191
|
+
Python 3.13 code imports `assay` from the distribution named `assay-engine`:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from assay import compose, parse_request
|
|
195
|
+
|
|
196
|
+
request = parse_request(
|
|
197
|
+
{
|
|
198
|
+
"method": "minimum",
|
|
199
|
+
"method_version": "service-health.v1",
|
|
200
|
+
"components": [
|
|
201
|
+
{
|
|
202
|
+
"id": "availability",
|
|
203
|
+
"label": "Availability",
|
|
204
|
+
"value": 99.9,
|
|
205
|
+
"scale": {"minimum": 99.0, "maximum": 100.0, "direction": "higher_is_better"},
|
|
206
|
+
"interval": None,
|
|
207
|
+
"weight": None,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"id": "latency",
|
|
211
|
+
"label": "Latency",
|
|
212
|
+
"value": 180.0,
|
|
213
|
+
"scale": {"minimum": 100.0, "maximum": 500.0, "direction": "lower_is_better"},
|
|
214
|
+
"interval": None,
|
|
215
|
+
"weight": None,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
"clamp": "reject",
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
result = compose(request)
|
|
223
|
+
print(result.score, result.selected_component_id)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The command line accepts typed JSON for `assay compose`, `assay measure`, and
|
|
227
|
+
`assay explain`. Build and installation commands for the unpublished checkout are in
|
|
228
|
+
the [quickstart](https://github.com/hseshadr/assay/blob/main/QUICKSTART.md).
|
|
229
|
+
|
|
230
|
+
## Optional calculators
|
|
231
|
+
|
|
232
|
+
Python's optional scientific surface calculates typed binary-classification, ranking,
|
|
233
|
+
calibration, agreement, and uncertainty reports. TypeScript exposes a smaller binary
|
|
234
|
+
and ranking calculator set. Complete optional-metric parity is not claimed, and the
|
|
235
|
+
calculator resource ceilings do not limit core composition. See
|
|
236
|
+
[Methods](https://github.com/hseshadr/assay/blob/main/docs/METHODS.md) and
|
|
237
|
+
[Operations](https://github.com/hseshadr/assay/blob/main/docs/OPERATIONS.md) for the
|
|
238
|
+
exact boundary.
|
|
239
|
+
|
|
240
|
+
## Optional integration
|
|
241
|
+
|
|
242
|
+
Assay computes scores; Avow seals evidence. They are separate products in separate repositories, and neither imports or requires the other. The already-published `avow` 0.4.1 and `@edgeproc/avow` 0.4.1 artifacts remain unchanged.
|
|
243
|
+
|
|
244
|
+
An application may pass an ordinary Assay result to a separately selected evidence
|
|
245
|
+
system. That adapter belongs to the application or to its own versioned integration
|
|
246
|
+
package, never to either core scoring package.
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT © Harish Seshadri
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Assay
|
|
2
|
+
|
|
3
|
+
> **TL;DR:** Assay combines measurements recorded on different scales into one explainable score while preserving every input, transformation, and contribution.
|
|
4
|
+
|
|
5
|
+
Assay is a small scoring engine for formulas you can write down and replay. Give it
|
|
6
|
+
measurements, their native scales, and one explicit combining method. It returns the
|
|
7
|
+
score and the arithmetic behind every row.
|
|
8
|
+
|
|
9
|
+
## Installation status
|
|
10
|
+
|
|
11
|
+
> **Status:** `assay-engine` 0.5.0.dev2 and `@edgeproc/assay` 0.5.0-dev.2 are local split candidates. Neither package is published.
|
|
12
|
+
|
|
13
|
+
The future authorized registry commands are `pip install assay-engine` and
|
|
14
|
+
`npm install @edgeproc/assay`. They are shown for identity only; do not run them until
|
|
15
|
+
a release is explicitly authorized. The runnable candidate path builds from this
|
|
16
|
+
checkout.
|
|
17
|
+
|
|
18
|
+
## Run the Northstar example
|
|
19
|
+
|
|
20
|
+
From the checkout root, run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bash examples/run_composite.sh
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The script builds the real Python wheel and npm tarball, installs each in an isolated
|
|
27
|
+
temporary environment, computes through both public package surfaces, checks every
|
|
28
|
+
typed field and binary64 value against the committed oracle, and prints one explanation:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
Northstar weighted score: 0.92
|
|
32
|
+
Method: weighted_mean @ northstar.2026-08-12
|
|
33
|
+
Interval: null — all inputs are deterministic
|
|
34
|
+
|
|
35
|
+
security 19/20 -> 0.950000 × 0.20 = 0.19
|
|
36
|
+
privacy 15/15 -> 1.000000 × 0.15 = 0.15
|
|
37
|
+
reliability 15/15 -> 1.000000 × 0.15 = 0.15
|
|
38
|
+
performance 12/15 -> 0.800000 × 0.15 = 0.12
|
|
39
|
+
correctness 15/15 -> 1.000000 × 0.15 = 0.15
|
|
40
|
+
clarity 14/15 -> 0.933333 × 0.15 = 0.14
|
|
41
|
+
production 2/5 -> 0.400000 × 0.05 = 0.02
|
|
42
|
+
|
|
43
|
+
Total: 0.92
|
|
44
|
+
inputs_hash: sha256:0266b1c59c97bacf85dc945685c55bb4386856b525249c7d5663a8edf020ba06
|
|
45
|
+
Parity: Python and TypeScript fields and values match
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This is uncapped arithmetic only. Northstar hard caps, evidence grades, release
|
|
49
|
+
decisions, and other product policies remain outside Assay.
|
|
50
|
+
|
|
51
|
+
## How the score is calculated
|
|
52
|
+
|
|
53
|
+
The example declares seven components on three native scales. Assay first normalizes
|
|
54
|
+
each value to 0–1, divides its positive weight by the declared total of 100, then adds
|
|
55
|
+
the contributions in declaration order:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
security: (19 - 0) / (20 - 0) × 20/100 = 0.19
|
|
59
|
+
privacy: (15 - 0) / (15 - 0) × 15/100 = 0.15
|
|
60
|
+
reliability: (15 - 0) / (15 - 0) × 15/100 = 0.15
|
|
61
|
+
performance: (12 - 0) / (15 - 0) × 15/100 = 0.12
|
|
62
|
+
correctness: (15 - 0) / (15 - 0) × 15/100 = 0.15
|
|
63
|
+
clarity: (14 - 0) / (15 - 0) × 15/100 = 0.14
|
|
64
|
+
production: ( 2 - 0) / ( 5 - 0) × 5/100 = 0.02
|
|
65
|
+
total: 0.92
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Assay's portable typed API supports exactly three composition methods:
|
|
69
|
+
|
|
70
|
+
- `weighted_mean` normalizes components, converts positive declared weights into
|
|
71
|
+
coefficients that sum to one, and adds their contributions.
|
|
72
|
+
- `additive` applies each raw term's explicit add or subtract operation and coefficient,
|
|
73
|
+
then optionally clamps the final total.
|
|
74
|
+
- `minimum` normalizes components and selects the first lowest value, making declaration
|
|
75
|
+
order the tie-breaker.
|
|
76
|
+
|
|
77
|
+
The method is chosen by the application because it owns the formula. Assay never
|
|
78
|
+
silently replaces a shipped formula with an average. See
|
|
79
|
+
[Methods](https://github.com/hseshadr/assay/blob/main/docs/METHODS.md) for validation,
|
|
80
|
+
uncertainty, and exact arithmetic rules.
|
|
81
|
+
|
|
82
|
+
### Legacy Python compatibility
|
|
83
|
+
|
|
84
|
+
The wheel retains a Python-only migration adapter at the deep import `assay.composite`:
|
|
85
|
+
`SubScore` plus `composite(...)`. It is not exported from the package root, does not
|
|
86
|
+
return the typed method or `inputs_hash` fields, and has no TypeScript equivalent. For
|
|
87
|
+
all new code, use package-root `parse_request()` and `compose()` with one of the three
|
|
88
|
+
portable methods above.
|
|
89
|
+
|
|
90
|
+
Every result field is explicit:
|
|
91
|
+
|
|
92
|
+
| Field | Meaning |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `schema` | Serialized result contract, currently `assay.result/v1`. |
|
|
95
|
+
| `method.id` | One of the three portable typed composition methods. |
|
|
96
|
+
| `method.version` | Caller-declared provenance for this formula revision. |
|
|
97
|
+
| `score` | Final finite binary64 result. |
|
|
98
|
+
| `interval` | Propagated uncertainty bounds, or `null` for deterministic inputs. |
|
|
99
|
+
| `clamp` | Requested boundary policy, or `null` only for unclamped additive scoring. |
|
|
100
|
+
| `intercept` | Additive starting value; `null` for the other methods. |
|
|
101
|
+
| `weight_total` | Weighted-mean declared weight total; otherwise `null`. |
|
|
102
|
+
| `components` | Ordered arithmetic rows retained for replay. |
|
|
103
|
+
| `id` | Stable input identifier for one row. |
|
|
104
|
+
| `raw` | Original finite input value; it may be sensitive. |
|
|
105
|
+
| `normalized` | 0–1 transformed value, or `null` for additive rows. |
|
|
106
|
+
| `declared_weight` | Original weighted-mean weight, otherwise `null`. |
|
|
107
|
+
| `operation` | `add` or `subtract`; normalized methods use `add`. |
|
|
108
|
+
| `coefficient` | Effective multiplier used for the row. |
|
|
109
|
+
| `contribution` | Pre-operation product: `normalized × coefficient` or `raw × coefficient`. For additive rows, `operation` controls how it changes the running total. |
|
|
110
|
+
| `contribution_interval` | Row uncertainty contribution, or `null`. |
|
|
111
|
+
| `inputs_hash` | Order-preserving request fingerprint used for replay comparison. |
|
|
112
|
+
| `selected_component_id` | Minimum-method bottleneck ID; otherwise `null`. |
|
|
113
|
+
|
|
114
|
+
Python and TypeScript parity covers the three methods, typed field/value structure,
|
|
115
|
+
field and component order, IEEE-754 binary64 values, and the exact `inputs_hash`. It
|
|
116
|
+
does not promise byte-identical output from language-native JSON serializers; for
|
|
117
|
+
example, one serializer may spell the same number `19.0` and another `19`.
|
|
118
|
+
|
|
119
|
+
## What this proves
|
|
120
|
+
|
|
121
|
+
For a validated request, the result exposes the selected method and version, preserves
|
|
122
|
+
the scored inputs in declaration order, shows every transformation and contribution,
|
|
123
|
+
and can be replayed under the same contract. The committed vectors prove the Python and
|
|
124
|
+
TypeScript composition surfaces agree semantically on all three methods and on the
|
|
125
|
+
exact request fingerprint.
|
|
126
|
+
|
|
127
|
+
## What this does not prove
|
|
128
|
+
|
|
129
|
+
Assay does not prove input truth, completeness, fairness, freshness, authenticity,
|
|
130
|
+
policy compliance, or decision quality. `inputs_hash` is a deterministic fingerprint,
|
|
131
|
+
not authentication or tamper evidence. A caller-declared method version records
|
|
132
|
+
provenance; it does not validate the methodology.
|
|
133
|
+
|
|
134
|
+
Application-owned bands, thresholds, hard gates, fairness review, abstention policy,
|
|
135
|
+
release decisions, and other downstream decisions remain application-owned. Results
|
|
136
|
+
retain raw values, so callers must treat them according to the sensitivity of their
|
|
137
|
+
inputs.
|
|
138
|
+
|
|
139
|
+
## Architecture
|
|
140
|
+
|
|
141
|
+
There are exactly two production source-to-artifact mappings:
|
|
142
|
+
|
|
143
|
+
```text
|
|
144
|
+
src/assay/ ──> assay-engine wheel ──> import assay
|
|
145
|
+
ts/src/ ──> @edgeproc/assay npm tarball ──> import "@edgeproc/assay"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`examples/`, `docs/`, `tests/`, and `testdata/` are repository support files, not
|
|
149
|
+
runtime packages. The Python package is the broader surface: composition is in the
|
|
150
|
+
base wheel, the command line uses the `cli` extra, and scientific calculators use the
|
|
151
|
+
`metrics` extra. The npm tarball provides composition plus a smaller set of optional
|
|
152
|
+
binary and ranking calculators.
|
|
153
|
+
|
|
154
|
+
This README is self-contained because the Python source distribution currently ships
|
|
155
|
+
it, but does not ship the repository's quickstart, docs, or examples. The detailed
|
|
156
|
+
[architecture](https://github.com/hseshadr/assay/blob/main/docs/ARCHITECTURE.md),
|
|
157
|
+
[operations contract](https://github.com/hseshadr/assay/blob/main/docs/OPERATIONS.md),
|
|
158
|
+
and [quickstart](https://github.com/hseshadr/assay/blob/main/QUICKSTART.md) are available
|
|
159
|
+
in the source checkout.
|
|
160
|
+
|
|
161
|
+
## Use the local candidate directly
|
|
162
|
+
|
|
163
|
+
Python 3.13 code imports `assay` from the distribution named `assay-engine`:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from assay import compose, parse_request
|
|
167
|
+
|
|
168
|
+
request = parse_request(
|
|
169
|
+
{
|
|
170
|
+
"method": "minimum",
|
|
171
|
+
"method_version": "service-health.v1",
|
|
172
|
+
"components": [
|
|
173
|
+
{
|
|
174
|
+
"id": "availability",
|
|
175
|
+
"label": "Availability",
|
|
176
|
+
"value": 99.9,
|
|
177
|
+
"scale": {"minimum": 99.0, "maximum": 100.0, "direction": "higher_is_better"},
|
|
178
|
+
"interval": None,
|
|
179
|
+
"weight": None,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"id": "latency",
|
|
183
|
+
"label": "Latency",
|
|
184
|
+
"value": 180.0,
|
|
185
|
+
"scale": {"minimum": 100.0, "maximum": 500.0, "direction": "lower_is_better"},
|
|
186
|
+
"interval": None,
|
|
187
|
+
"weight": None,
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
"clamp": "reject",
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
result = compose(request)
|
|
195
|
+
print(result.score, result.selected_component_id)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The command line accepts typed JSON for `assay compose`, `assay measure`, and
|
|
199
|
+
`assay explain`. Build and installation commands for the unpublished checkout are in
|
|
200
|
+
the [quickstart](https://github.com/hseshadr/assay/blob/main/QUICKSTART.md).
|
|
201
|
+
|
|
202
|
+
## Optional calculators
|
|
203
|
+
|
|
204
|
+
Python's optional scientific surface calculates typed binary-classification, ranking,
|
|
205
|
+
calibration, agreement, and uncertainty reports. TypeScript exposes a smaller binary
|
|
206
|
+
and ranking calculator set. Complete optional-metric parity is not claimed, and the
|
|
207
|
+
calculator resource ceilings do not limit core composition. See
|
|
208
|
+
[Methods](https://github.com/hseshadr/assay/blob/main/docs/METHODS.md) and
|
|
209
|
+
[Operations](https://github.com/hseshadr/assay/blob/main/docs/OPERATIONS.md) for the
|
|
210
|
+
exact boundary.
|
|
211
|
+
|
|
212
|
+
## Optional integration
|
|
213
|
+
|
|
214
|
+
Assay computes scores; Avow seals evidence. They are separate products in separate repositories, and neither imports or requires the other. The already-published `avow` 0.4.1 and `@edgeproc/avow` 0.4.1 artifacts remain unchanged.
|
|
215
|
+
|
|
216
|
+
An application may pass an ordinary Assay result to a separately selected evidence
|
|
217
|
+
system. That adapter belongs to the application or to its own versioned integration
|
|
218
|
+
package, never to either core scoring package.
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
MIT © Harish Seshadri
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling==1.27.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "assay-engine"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Explainable composition of measurements recorded on heterogeneous scales."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Harish Seshadri", email = "harish.seshadri@gmail.com" }]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pydantic>=2.11",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
metrics = [
|
|
28
|
+
"pydantic-settings>=2.11",
|
|
29
|
+
"scikit-learn>=1.9", # precision/recall/F1, PR-AUC, ROC-AUC, Brier
|
|
30
|
+
"scipy>=1.18", # scipy.stats.bootstrap confidence intervals
|
|
31
|
+
"numpy>=2.5", # array math shared by sklearn + scipy
|
|
32
|
+
# trec_eval's own arithmetic for the ranking face (nDCG, AP, P@k, R@k, RR). Exactly
|
|
33
|
+
# one transitive dependency — pytrec-eval-terrier, the C++ binding to the reference
|
|
34
|
+
# implementation — whose own deps are numpy + scipy, already pinned right above.
|
|
35
|
+
"ir-measures>=0.4.3",
|
|
36
|
+
]
|
|
37
|
+
cli = [
|
|
38
|
+
"typer>=0.27",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"hatchling==1.27.0",
|
|
44
|
+
"pytest>=9.1.1",
|
|
45
|
+
"pytest-cov>=6.0",
|
|
46
|
+
"mypy>=2.2.0",
|
|
47
|
+
"ruff>=0.15.21",
|
|
48
|
+
"xenon>=0.9",
|
|
49
|
+
"radon>=6.0",
|
|
50
|
+
"poethepoet>=0.48.0",
|
|
51
|
+
"pyyaml>=6.0.2,<7",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
assay = "assay.cli:main"
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://github.com/hseshadr/assay"
|
|
59
|
+
Repository = "https://github.com/hseshadr/assay"
|
|
60
|
+
Issues = "https://github.com/hseshadr/assay/issues"
|
|
61
|
+
|
|
62
|
+
# Hatchling and ``assay.__version__`` read the same source.
|
|
63
|
+
[tool.hatch.version]
|
|
64
|
+
path = "src/assay/_version.py"
|
|
65
|
+
|
|
66
|
+
[tool.hatch.build.targets.wheel]
|
|
67
|
+
packages = ["src/assay"]
|
|
68
|
+
|
|
69
|
+
[tool.hatch.build.targets.sdist]
|
|
70
|
+
include = [
|
|
71
|
+
"/LICENSE",
|
|
72
|
+
"/README.md",
|
|
73
|
+
"/pyproject.toml",
|
|
74
|
+
"/src/assay",
|
|
75
|
+
]
|
|
76
|
+
exclude = ["/.gitignore"]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
target-version = "py313"
|
|
80
|
+
line-length = 100
|
|
81
|
+
src = ["src", "tests"]
|
|
82
|
+
exclude = [".git", "__pycache__", "build", "dist", ".venv", "venv"]
|
|
83
|
+
|
|
84
|
+
[tool.ruff.lint]
|
|
85
|
+
select = ["E", "F", "I", "W", "C901", "B", "UP", "SIM", "N", "RUF", "ASYNC", "S", "PL", "A", "PT"]
|
|
86
|
+
ignore = ["S101", "PLR0913"]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.per-file-ignores]
|
|
89
|
+
"tests/*" = ["S101", "PLR2004", "C901", "PLR0911", "PLR0912"]
|
|
90
|
+
"tests/test_cli.py" = ["S603", "S607"]
|
|
91
|
+
"tests/test_cli_migration.py" = ["S603", "S607"]
|
|
92
|
+
"tests/test_cli_privacy.py" = ["S603", "S607"]
|
|
93
|
+
"tests/test_measurement.py" = ["S603", "S607"]
|
|
94
|
+
"tests/test_release_contract.py" = ["S603", "S607"]
|
|
95
|
+
"tests/test_workflow_contract.py" = ["S603", "S607"]
|
|
96
|
+
# Domain errors use semantic names such as InvalidRankingRequest. They are the stable,
|
|
97
|
+
# coded interface consumed across Assay's calculators, CLI, and tests.
|
|
98
|
+
"src/assay/errors.py" = ["N818"]
|
|
99
|
+
# ir-measures intentionally exports its standard measure as ``nDCG``.
|
|
100
|
+
"src/assay/ranking.py" = ["N816"]
|
|
101
|
+
|
|
102
|
+
[tool.ruff.lint.mccabe]
|
|
103
|
+
max-complexity = 5
|
|
104
|
+
|
|
105
|
+
[tool.ruff.lint.isort]
|
|
106
|
+
known-first-party = ["assay"]
|
|
107
|
+
extra-standard-library = ["fcntl"]
|
|
108
|
+
|
|
109
|
+
[tool.mypy]
|
|
110
|
+
python_version = "3.13"
|
|
111
|
+
strict = true
|
|
112
|
+
warn_return_any = true
|
|
113
|
+
warn_unused_configs = true
|
|
114
|
+
warn_unreachable = true
|
|
115
|
+
disallow_untyped_defs = true
|
|
116
|
+
mypy_path = "src"
|
|
117
|
+
|
|
118
|
+
# These third-party libraries ship no type stubs; assay wraps them behind its
|
|
119
|
+
# own typed contracts, so import-time Any is contained at the boundary.
|
|
120
|
+
[[tool.mypy.overrides]]
|
|
121
|
+
module = ["sklearn.*", "scipy.*"]
|
|
122
|
+
ignore_missing_imports = true
|
|
123
|
+
|
|
124
|
+
[tool.pytest.ini_options]
|
|
125
|
+
minversion = "8.3"
|
|
126
|
+
testpaths = ["tests"]
|
|
127
|
+
python_files = ["test_*.py"]
|
|
128
|
+
addopts = [
|
|
129
|
+
"--strict-markers",
|
|
130
|
+
"-v",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[tool.coverage.report]
|
|
134
|
+
exclude_lines = [
|
|
135
|
+
"pragma: no cover",
|
|
136
|
+
"def __repr__",
|
|
137
|
+
"raise AssertionError",
|
|
138
|
+
"raise NotImplementedError",
|
|
139
|
+
"if __name__ == .__main__.:",
|
|
140
|
+
"if TYPE_CHECKING:",
|
|
141
|
+
"@abstractmethod",
|
|
142
|
+
# Anchored to a bare `...` stub body ONLY. The unanchored `\.\.\.` this
|
|
143
|
+
# replaces also matched the ellipsis inside `tuple[X, ...]` annotations, which
|
|
144
|
+
# silently excluded every function whose annotation carried one. Coverage must not
|
|
145
|
+
# be able to hide code based on an annotation.
|
|
146
|
+
"^\\s*\\.\\.\\.$",
|
|
147
|
+
]
|
|
148
|
+
fail_under = 90.0
|
|
149
|
+
|
|
150
|
+
[tool.poe.tasks]
|
|
151
|
+
# scripts/ is held to the same bar as src/ and tests/: the mutation harness is the thing
|
|
152
|
+
# that proves the guards can fail, so it is not exempt from the gate that guards them.
|
|
153
|
+
lint = "ruff check src/ tests/ scripts/ benchmarks/"
|
|
154
|
+
fmt-check = "ruff format --check src/ tests/ scripts/ benchmarks/"
|
|
155
|
+
fmt = "ruff format src/ tests/ scripts/ benchmarks/"
|
|
156
|
+
complexity = "xenon --max-absolute A --max-modules A --max-average A src/assay scripts benchmarks"
|
|
157
|
+
test = "pytest --cov=assay --cov-branch --cov-report=term-missing --cov-fail-under=90"
|
|
158
|
+
gate = ["lint", "fmt-check", "typecheck", "complexity", "test"]
|
|
159
|
+
|
|
160
|
+
mutants = "python scripts/mutation_harness.py"
|
|
161
|
+
benchmark = "python -m benchmarks.release"
|
|
162
|
+
workflow-contract = "pytest tests/test_workflow_contract.py tests/test_release_contract.py -q"
|
|
163
|
+
workflow-lint = "actionlint"
|
|
164
|
+
workflow-security = "uvx zizmor==1.29.0 --persona=pedantic --min-severity=low --offline --strict-collection .github"
|
|
165
|
+
secrets = { shell = "gitleaks git --redact --no-banner --log-opts=--all . && gitleaks dir --redact --no-banner ." }
|
|
166
|
+
audit-python = { shell = "uv export --frozen --all-groups --all-extras --no-emit-project --format requirements.txt --output-file /tmp/assay-audit-requirements.txt >/dev/null && uvx --from pip-audit==2.10.1 pip-audit --requirement /tmp/assay-audit-requirements.txt --require-hashes --strict --disable-pip --progress-spinner off" }
|
|
167
|
+
audit-typescript = { shell = "pnpm --dir ts install --frozen-lockfile --ignore-scripts && pnpm --dir ts audit --audit-level high" }
|
|
168
|
+
audit = ["audit-python", "audit-typescript"]
|
|
169
|
+
artifacts = "bash scripts/build_release_artifacts.sh dist/release"
|
|
170
|
+
release-candidate = "bash scripts/verify_release_candidate.sh"
|
|
171
|
+
|
|
172
|
+
[tool.poe.tasks.typecheck]
|
|
173
|
+
cmd = "mypy --strict src/assay scripts benchmarks"
|