structured-data-models-client 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,46 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
2
+ # All rights reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ r"""The Universal TFM wire format, in one place.
6
+
7
+ Every model posts the same envelope to ``POST /v1/predictions``; the server
8
+ tells them apart by the ``model`` field alone. These modules own the parts of
9
+ that envelope that are the same whatever the model is: how a column's dtype is
10
+ named, how a cell is written, and how a frame becomes an ``arrays`` table.
11
+
12
+ The boundary is DataFrames plus plain metadata. Nothing here knows what a
13
+ graph, a subgraph or a predictive query is, which is what lets the relational
14
+ path reach the same code: its adapter samples first and arrives with frames
15
+ like any other.
16
+ """
17
+
18
+ from sdm_client.wire.dtypes import (
19
+ JSON_SAFE_INT_MAX,
20
+ JSON_SAFE_INT_MIN,
21
+ UNTYPED,
22
+ as_declared,
23
+ infer_tfm_dtype,
24
+ is_container,
25
+ json_ready,
26
+ require_scalar_cells,
27
+ serialize_cell,
28
+ serialize_column,
29
+ widen_tfm_dtype,
30
+ )
31
+ from sdm_client.wire.tables import encode_table
32
+
33
+ __all__ = [
34
+ 'JSON_SAFE_INT_MAX',
35
+ 'JSON_SAFE_INT_MIN',
36
+ 'UNTYPED',
37
+ 'as_declared',
38
+ 'encode_table',
39
+ 'infer_tfm_dtype',
40
+ 'is_container',
41
+ 'json_ready',
42
+ 'require_scalar_cells',
43
+ 'serialize_cell',
44
+ 'serialize_column',
45
+ 'widen_tfm_dtype',
46
+ ]
@@ -0,0 +1,80 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
2
+ # All rights reserved.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ r"""Giving an answer back in the type the question was asked in.
6
+
7
+ Both models answer with a class label the column already held, and both
8
+ spell it on the wire as text. Restoring the column's own type is therefore
9
+ about the response rather than about either model, and lives here rather
10
+ than with one of them.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import dataclasses
16
+ from typing import Any
17
+
18
+ from sdm_client.wire.dtypes import as_declared
19
+
20
+
21
+ def as_declared_target(items: list[Any], payload: dict[str, Any] | None):
22
+ r"""Give `prediction` back as the type the target column holds.
23
+
24
+ One endpoint renders a class label as a string and the other sends the
25
+ number, for the same question on the same integer-coded target. Both are
26
+ legal and the values agree; only their types differ, so
27
+
28
+ sum(1 for row in result if row.prediction == 0)
29
+
30
+ This counted one of five on one model and none of five on the other. There
31
+ was no exception, only a plausible-looking answer wrong in the direction
32
+ of "this class never occurs". The target's dtype is on the wire, so it is
33
+ read back with it.
34
+
35
+ Class labels only, and a `> 0` target's labels are a bool, which one
36
+ endpoint spells `'false'`. They are values the column held, so giving them
37
+ back as the column's own type is restoring something. A regression's answer
38
+ is a number the model computed. Rounding it to the target's dtype would
39
+ make this client decide how precise the answer is.
40
+
41
+ The same labels key `probabilities` and are restored there too.
42
+ """
43
+
44
+ task = (payload or {}).get('task', {})
45
+ if 'classification' not in str(task.get('kind', '')):
46
+ return items
47
+ dtype = task.get('target', {}).get('dtype')
48
+ if not dtype or not dtype.startswith(('int', 'float', 'bool')):
49
+ return items
50
+ read = []
51
+ for item in items:
52
+ changed: dict[str, Any] = {}
53
+ value = getattr(item, 'prediction', None)
54
+ restored = as_declared(value, dtype)
55
+ if restored is not value:
56
+ changed['prediction'] = restored
57
+ given = getattr(item, 'probabilities', None)
58
+ if given:
59
+ keyed = _as_the_column_spells_them(given, dtype)
60
+ if keyed != given:
61
+ changed['probabilities'] = keyed
62
+ read.append(dataclasses.replace(item, **changed) if changed else item)
63
+ return read
64
+
65
+
66
+ def _as_the_column_spells_them(
67
+ probabilities: dict[str, Any], dtype: str
68
+ ) -> dict[str, Any]:
69
+ r"""The probability mapping, keyed by each class as the column spells it.
70
+
71
+ One endpoint keys a bool target `'false'` and the other `'False'`, so
72
+ `frame['probability_true']` raises depending on which answered.
73
+
74
+ Left alone where restoring would collapse two classes into one.
75
+ """
76
+ renamed = {}
77
+ for key, value in probabilities.items():
78
+ restored = as_declared(key, dtype)
79
+ renamed[key if restored is key else str(restored)] = value
80
+ return renamed if len(renamed) == len(probabilities) else probabilities