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.
Files changed (29) hide show
  1. assay_engine-0.5.0.dev2/LICENSE +21 -0
  2. assay_engine-0.5.0.dev2/PKG-INFO +250 -0
  3. assay_engine-0.5.0.dev2/README.md +222 -0
  4. assay_engine-0.5.0.dev2/pyproject.toml +173 -0
  5. assay_engine-0.5.0.dev2/src/assay/__init__.py +83 -0
  6. assay_engine-0.5.0.dev2/src/assay/_cli_app.py +102 -0
  7. assay_engine-0.5.0.dev2/src/assay/_cli_io.py +130 -0
  8. assay_engine-0.5.0.dev2/src/assay/_json.py +34 -0
  9. assay_engine-0.5.0.dev2/src/assay/_optional.py +69 -0
  10. assay_engine-0.5.0.dev2/src/assay/_version.py +5 -0
  11. assay_engine-0.5.0.dev2/src/assay/additive.py +104 -0
  12. assay_engine-0.5.0.dev2/src/assay/agreement.py +312 -0
  13. assay_engine-0.5.0.dev2/src/assay/calibration.py +167 -0
  14. assay_engine-0.5.0.dev2/src/assay/cli.py +55 -0
  15. assay_engine-0.5.0.dev2/src/assay/compose.py +31 -0
  16. assay_engine-0.5.0.dev2/src/assay/composite.py +228 -0
  17. assay_engine-0.5.0.dev2/src/assay/contracts.py +886 -0
  18. assay_engine-0.5.0.dev2/src/assay/errors.py +166 -0
  19. assay_engine-0.5.0.dev2/src/assay/limits.py +13 -0
  20. assay_engine-0.5.0.dev2/src/assay/measurement.py +1325 -0
  21. assay_engine-0.5.0.dev2/src/assay/metrics.py +238 -0
  22. assay_engine-0.5.0.dev2/src/assay/minimum.py +82 -0
  23. assay_engine-0.5.0.dev2/src/assay/models.py +111 -0
  24. assay_engine-0.5.0.dev2/src/assay/normalize.py +74 -0
  25. assay_engine-0.5.0.dev2/src/assay/py.typed +0 -0
  26. assay_engine-0.5.0.dev2/src/assay/ranking.py +361 -0
  27. assay_engine-0.5.0.dev2/src/assay/settings.py +90 -0
  28. assay_engine-0.5.0.dev2/src/assay/uncertainty.py +190 -0
  29. 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"