@zhuoyuezs/ml-platform 0.2.7 → 0.2.9

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.
@@ -7,13 +7,25 @@ Read this file whenever a workflow creates or changes executable Operator code.
7
7
  1. Package contract
8
8
  2. Feature entrypoint contract
9
9
  3. Causal calculation rules
10
- 4. Required tests
11
- 5. Build and publication
12
- 6. Security boundary
10
+ 4. Online period aggregates
11
+ 5. Required tests
12
+ 6. Build and publication
13
+ 7. Security boundary
13
14
 
14
15
  ## Package Contract
15
16
 
16
- Build a standalone pure-Python wheel. Do not import report-local, experiment-local, or unregistered business packages. Declare runtime dependencies explicitly, but keep the package small and compatible with `py3-none-any`.
17
+ Build a standalone pure-Python wheel (`py3-none-any`). The platform verifies
18
+ the wheel digest and only extracts it into an isolated import path; it does not
19
+ install `Requires-Dist` dependencies, run `pip`, or accept native code. At run
20
+ time an Operator can import only the Python standard library, its own wheel,
21
+ and packages already present in the image that executes it: dataset builds
22
+ (the `dataset-chronon` dependency group, which provides `pandas`, `numpy` via
23
+ `pandas`, `pyarrow` and `pydantic`) and API realtime reads (the `api`
24
+ dependency group). Do not import report-local, experiment-local, or
25
+ unregistered business packages, and do not depend on libraries such as `scipy`
26
+ or `scikit-learn` that those images do not provide; vendor small pure-Python
27
+ helpers into the wheel instead. `[project].dependencies` documents the expected
28
+ environment for local tests only; it does not make a dependency available.
17
29
 
18
30
  Minimal `pyproject.toml`:
19
31
 
@@ -63,9 +75,19 @@ computation_hash
63
75
 
64
76
  Each `metric_frames[parameter_key]` is a standard long frame with source timestamps and values. Resolve frames from `context.inputs`; do not reach into a database, API, filesystem secret, or environment credential.
65
77
 
66
- Use `context.cutoff_times` for causal feature windows. It is derived once from
67
- `context.target_times - context.prediction_horizon` using the DatasetManifest
68
- prediction contract; do not parse a duplicated Feature-level horizon.
78
+ Use `context.cutoff_times` for causal feature windows. `context.target_times`
79
+ are the materialized rows' `event_time` values, which are the prediction
80
+ decision times, and `context.cutoff_times` equals them: a row's feature
81
+ cutoff is its own `event_time`. The DatasetManifest `prediction.horizon` moves
82
+ label availability to `label_time = event_time + horizon` (and the realtime
83
+ response `target_time = cutoff_time + horizon`); it does not move the Operator
84
+ context's feature reads earlier. Do not subtract `context.prediction_horizon`
85
+ from either index, and do not parse a duplicated Feature-level horizon. This
86
+ rule covers Operator inputs only: a DatasetManifest `endpoint_policy` evaluates
87
+ abnormal-window context overlap with its own `cutoff = t - h` formula, described
88
+ in [contracts.md](contracts.md). Resolved lineage records this as
89
+ `prediction_time_semantics: event_time_is_causal_cutoff` and
90
+ `label_time_derivation: prediction_time_plus_horizon`.
69
91
 
70
92
  An Operator requires an explicit forecast contract whenever its formula or code
71
93
  uses `context.cutoff_times`, or any `input_schema.history_requirements` entry uses
@@ -100,7 +122,11 @@ One Operator may return multiple physical columns. Each public Feature still bin
100
122
 
101
123
  ## Causal Calculation Rules
102
124
 
103
- State every time rule in tests and descriptions:
125
+ State every time rule in tests and descriptions. A formula that needs the
126
+ complete requested window rather than a bounded causal lookback (for example
127
+ full-window interpolation or smoothing) is not partition-invariant; document
128
+ that the Dataset must be built with `--partition-duration none
129
+ --max-parallelism 1` (see [commands.md](commands.md)).
104
130
 
105
131
  - forecast horizon and cutoff derivation;
106
132
  - whether an event exactly at cutoff is included (`<=`) or excluded (`<`);
@@ -113,6 +139,72 @@ State every time rule in tests and descriptions:
113
139
 
114
140
  Never infer a rule from a Feature name alone. Require a business decision when a boundary is unspecified.
115
141
 
142
+ ## Online Period Aggregates
143
+
144
+ Use this method when one Feature row summarizes a whole period (for example a
145
+ day, shift or batch window) and the Feature must also be served online through
146
+ `fetch-inference-data` or a model's `feature_lookup`. The realtime path gives
147
+ the Operator exactly one target row, `event_time = cutoff`, and only source
148
+ rows with `timestamp <= cutoff`, so the batch and online values agree only when
149
+ the formula is causal and bounded.
150
+
151
+ Row semantics:
152
+
153
+ - Anchor the row at the period end: the row at cutoff `T` summarizes the
154
+ period that ended at `T` (for a daily Feature, `T` is local midnight and the
155
+ period is the previous day). Never label a row with the period start and
156
+ aggregate data after it.
157
+ - Decide the period boundary from the source timestamp meaning, which must be
158
+ confirmed by the data owner (see [contracts.md](contracts.md), realtime
159
+ policy questions). If an hourly record is stamped at the hour start, the
160
+ record stamped `T` belongs to the next period, so use `[T - period, T)`. If it
161
+ is stamped at the hour end, the record stamped `T` closes the period, so use
162
+ `(T - period, T]`. Test the record exactly at `T` either way.
163
+
164
+ Contract declarations:
165
+
166
+ - `supports_online: true` on the Operator and `offline_online_supported: true`
167
+ on each Feature that will be served online.
168
+ - `input_schema.history_requirements` with `anchor: cutoff` and a lookback that
169
+ covers the aggregation period, any multi-period context (for example a
170
+ 3-day rolling statistic), and alignment slack for source timestamps. For
171
+ example, a daily mean that also uses the two previous days needs at least
172
+ `3d` plus slack. This lookback is also the per-Parameter realtime read
173
+ window; the default realtime floor `min_lookback=1h` is far too short for a
174
+ period aggregate, and it is not a substitute for the declaration.
175
+ - `input_schema.prediction` with `required`, `minimum_horizon` and
176
+ `maximum_horizon` (a strict nowcast uses `true`, `0min`, `0min`).
177
+ - Do not use `anchor: target_time` with zero lookback for a period aggregate;
178
+ online that reads almost nothing.
179
+
180
+ Formula rules:
181
+
182
+ - Use only rows inside the declared lookback that are at or before the row's
183
+ cutoff. A row's value must not change when data after its cutoff is added,
184
+ or when history older than the lookback is added or removed.
185
+ - Freeze fitted constants in Feature `config`. Values fitted on training data,
186
+ such as fallback medians, clipping limits or normal ranges, must be computed
187
+ once offline, written into `config`, and versioned with the Feature. Do not
188
+ recompute them from the window at run time: the online window holds only
189
+ the lookback, so a recomputed value differs between batch and online and
190
+ between partitions. A retrain that refits these constants needs a new
191
+ Feature (and usually Operator) version.
192
+ - Fill gaps only forward in time, and bound each fill by a maximum age or a
193
+ frozen constant. Never backward-fill from an observation after the row's
194
+ cutoff. Avoid backward-filling the start of the window too: the result then
195
+ depends on where the window starts and breaks partition invariance.
196
+ - The Operator owns interior gap handling. The platform's realtime tolerances
197
+ (`allow_tail_edge_missing`, `allow_internal_gap_fill`) only judge how far the
198
+ newest source row lags the cutoff; they do not insert values. Keep one
199
+ missing-data rule, inside the Operator or a declared Parameter rule, so batch
200
+ builds and realtime reads compute the same value.
201
+ - Emit data-quality flags as their own output columns, each bound to its own
202
+ Feature: for example a valid-observation count, an "insufficient data" flag,
203
+ or a business state flag such as "plant stopped". A flag tells consumers
204
+ that a row was computed under a fallback rule; do not hide that by silently
205
+ filling the value. Include a flag in the model FeatureSet only if it is an
206
+ intended model input.
207
+
116
208
  ## Required Tests
117
209
 
118
210
  Use deterministic fixtures and cover each applicable item with a separately
@@ -131,6 +223,22 @@ requested-output, event-time-order, or dtype/rounding cases below:
131
223
  9. output dtype and rounding;
132
224
  10. every dated business-rule branch.
133
225
 
226
+ For an online-capable Operator (`supports_online: true`), also add:
227
+
228
+ 11. cutoff invariance: adding source rows after the cutoff leaves the row
229
+ unchanged;
230
+ 12. lookback invariance: adding or removing history older than the declared
231
+ lookback leaves the row unchanged;
232
+ 13. partition invariance: computing many cutoffs in one call equals computing
233
+ each cutoff alone with only its lookback window, which is what a realtime
234
+ read does;
235
+ 14. fallback rows: a period with no or too few valid observations produces the
236
+ documented fallback value and quality flag, using the frozen constants.
237
+
238
+ After publication, compare `fetch-inference-data` at a few historical cutoffs
239
+ with the same rows of a batch DatasetArtifact. Any difference means the
240
+ Operator, its fill rule or its lookback is not causal; fix it before training.
241
+
134
242
  Run package tests before building the wheel. Then inspect the wheel tag and ensure it is `py3-none-any`.
135
243
 
136
244
  ## Build And Publication
@@ -35,6 +35,9 @@ deployment does not reach `READY` or an operation is rejected.
35
35
  the model capacity and sampling grid. In `feature_lookup` mode, never send
36
36
  `future_covariates`; the platform retrieves the complete known-future Feature
37
37
  window visible at the cutoff. Inline mode still supplies those rows in inputs.
38
+ - Clustering deployments take no `inference_policy`, `horizon`, or
39
+ `output_window`; an optional `include_inputs: true` echoes the model input
40
+ row. See the clustering request in [references/operations.md](references/operations.md).
38
41
  - Run `validate-deployment` before `create-deployment`. Validation is read-only
39
42
  and does not authorize creation.
40
43
  - Obtain explicit authorization for the exact deployment and operation before
@@ -39,7 +39,11 @@ ml-platform --profile server list-deployments \
39
39
  --model-artifact-id <artifact-id>
40
40
  ```
41
41
 
42
- The filters may be used independently and combined with `--service`,
42
+ 列表默认按 `deployment_id` 升序;需要按生命周期时间排序时使用
43
+ `--sort-by created_at|updated_at --sort-order asc|desc`。
44
+
45
+ Use `--service TEXT` for a case-insensitive service-name substring; surrounding
46
+ whitespace is ignored and an empty value does not filter. The filter may be combined with
43
47
  `--environment`, `--state`, `--q`, `--limit`, and `--offset`. These results are
44
48
  ITSMP platform deployments; external model-center delivery callbacks are a
45
49
  separate status surface.
@@ -157,6 +161,15 @@ ml-platform --profile server predict-deployment <deployment-id> \
157
161
  /absolute/path/request.json
158
162
  ```
159
163
 
164
+ For a clustering model (`task_type: clustering` in `GET /v1/model`), send no
165
+ `horizon` or `output_window`; they are rejected. The single result is
166
+ `prediction: {kind: "clustering", cluster_id, distance_to_center |
167
+ cluster_probability}` without `target_time`. Add `"include_inputs": true` to
168
+ receive `results[0].inputs`, the validated model input row in signature column
169
+ order (before the model's own transform); it defaults to `false`, is rejected
170
+ for forecasting models, and requires a package image that supports it. Missing
171
+ or non-finite retrieved inputs fail the request.
172
+
160
173
  For a package with an explicit `availability_boundary`, inspect
161
174
  `lineage.visibility` in the prediction response. It records the package's
162
175
  boundary, `cutoff_time`, `attempt_started_at`, `knowledge_time`,
@@ -36,6 +36,22 @@ uses platform CLI contracts and works without an ITSMP TUI or a specific Agent
36
36
  Runtime. An execution host may additionally persist plans, method hashes,
37
37
  attempts and checkpoints; those records do not replace platform evidence.
38
38
 
39
+ For unsupervised clustering (`task.kind=clustering`, for example
40
+ `kmeans_clusterer` or `gaussian_mixture_clusterer`), read the clustering
41
+ section of [references/training-contracts.md](references/training-contracts.md)
42
+ for the Run, parameter and model-file contract, and the clustering section of
43
+ [references/packaging.md](references/packaging.md) for packaging and
44
+ single-row inference without a horizon. The cluster count and preprocessing
45
+ have silent defaults (8 clusters for KMeans, no scaling or PCA). Ask the user
46
+ for them, or select them with the documented method in that reference, before
47
+ submitting a clustering Run.
48
+
49
+ For clustering evaluation and registration, read the clustering section of
50
+ [references/evaluation.md](references/evaluation.md). Use a `task_type:
51
+ "clustering"` EvaluationConfig with `targets: ["cluster_id"]`, and label
52
+ `all`/`train` rowset results as in-sample. Register only with one PASS,
53
+ COMPLETE EvaluationResult for that artifact.
54
+
39
55
  Read [references/zero-shot-inference.md](references/zero-shot-inference.md) when
40
56
  the user wants synchronous zero-shot forecasting from JSON or CSV. This path
41
57
  uses the platform-owned `chronos2_forecaster` model name, not a public project
@@ -65,6 +65,20 @@ Jobs in lists are immutable specs; use the exact get-job command for refreshed
65
65
  status. Lists do not reconcile or schedule workloads. `get-training-run` returns
66
66
  `training_run` plus `spec_hash`; artifact/package detail returns the object.
67
67
 
68
+ ## Download artifact files
69
+
70
+ ```bash
71
+ ml-platform --profile server download-model-artifact ARTIFACT_ID --out-dir /tmp/model-ARTIFACT_ID [--file runtime.json]
72
+ ml-platform --profile server download-evaluation-result-artifact RESULT_ID --out-dir /tmp/eval-RESULT_ID [--file predictions.parquet]
73
+ ```
74
+
75
+ Both commands download only files indexed by the registry record (all of them
76
+ unless `--file` is repeated), verify each sha256 against that record, and remove
77
+ the output directory on any mismatch. `--project` fails closed when the
78
+ resource belongs to another project. Report the returned `files[].sha256` as
79
+ content evidence; metadata reads alone do not prove file contents. Write
80
+ downloads outside the repository.
81
+
68
82
  ## Read-only preflight
69
83
 
70
84
  ```bash
@@ -97,10 +111,11 @@ Training Run `task`, `data`, `data.artifact`, `features.feature_set`, `splits`,
97
111
  config references, members and execution, plus ModelPackage requests, are also
98
112
  structured. Unknown keys return 422 with `detail[].loc` identifying the field.
99
113
  Keep algorithm-specific `trainer.parameters` governed by the SystemModel
100
- parameter schema. A typed nested contract such as Chronos-2 `lora_config`
114
+ parameter schema. A typed contract such as Chronos-2 `lora_config` or the
115
+ KMeans/Gaussian Mixture clustering parameters (including `preprocessing`)
101
116
  rejects unknown fields; a SystemModel that intentionally advertises
102
- `additionalProperties` remains extensible. Do not infer one policy from the
103
- other.
117
+ `additionalProperties` remains extensible. Read `additionalProperties` from
118
+ the exact SystemModel instead of inferring one model's policy from another.
104
119
 
105
120
  These HTTP checks preserve accepted values and do not rewrite historical stored
106
121
  contracts or hashes. Legacy records remain readable. A previously ignored typo
@@ -100,6 +100,138 @@ not prove Worker dependency availability. Validate before submitting and confirm
100
100
  the approved Worker supports the model plugin. Successful training in another
101
101
  image does not establish evaluation support.
102
102
 
103
+ ## Clustering evaluation
104
+
105
+ A clustering ModelArtifact (with `output_bindings`) is evaluated only by an
106
+ EvaluationConfig with `task_type: "clustering"`. Supervised configs reject
107
+ clustering artifacts, and clustering configs reject supervised artifacts.
108
+ There are no labels: the Worker loads the frozen model, predicts assignments
109
+ for the requested rowset, and computes metrics. It never refits the model or
110
+ its preprocessing and never relabels clusters.
111
+
112
+ Example release `evaluation-config.json`. The thresholds are placeholders and
113
+ must be business-approved before use:
114
+
115
+ ```json
116
+ {
117
+ "name": "process_regimes_release",
118
+ "version": "v1",
119
+ "task_type": "clustering",
120
+ "mode": "release",
121
+ "metrics": [
122
+ {"name": "clustering.silhouette", "calculation_space": "transformed"},
123
+ {"name": "clustering.davies_bouldin", "calculation_space": "transformed"},
124
+ {"name": "clustering.cluster_count"},
125
+ {"name": "clustering.min_cluster_share"}
126
+ ],
127
+ "validation": [
128
+ {"metric": "clustering.silhouette", "operator": ">=", "threshold": 0.25},
129
+ {"metric": "clustering.min_cluster_share", "operator": ">=", "threshold": 0.05}
130
+ ],
131
+ "clustering": {"sample_limit": 5000, "sample_seed": 0}
132
+ }
133
+ ```
134
+
135
+ The `clustering` object is optional and accepted only for `task_type:
136
+ "clustering"`:
137
+
138
+ - `sample_limit` (>= 2, default 5000) bounds the silhouette computation. Larger
139
+ rowsets are sampled deterministically with `sample_seed` (default 0). Other
140
+ metrics use every valid row.
141
+ - `reference_label_column` (optional) names an independent DataSchema column
142
+ with role `label` or `metadata`, never a model feature. It is required by the
143
+ external metrics `clustering.adjusted_rand_index` and
144
+ `clustering.normalized_mutual_info`, and is only a comparison reference,
145
+ never a training input.
146
+ - `distance_metric` currently accepts only `euclidean`.
147
+
148
+ Clustering configs reject `baseline`, comparison rules, sample weights and
149
+ package (code) metrics. Built-in `clustering.*` metrics and expression metrics
150
+ over them are supported:
151
+
152
+ | Metric | Inputs | Direction | Calculation spaces |
153
+ | --- | --- | --- | --- |
154
+ | `clustering.silhouette` | features, assignment | maximize | `business`, `transformed` |
155
+ | `clustering.davies_bouldin` | features, assignment | minimize | `business`, `transformed` |
156
+ | `clustering.calinski_harabasz` | features, assignment | maximize | `business`, `transformed` |
157
+ | `clustering.cluster_count` | assignment | neutral | `business` |
158
+ | `clustering.min_cluster_share` | assignment | maximize | `business` |
159
+ | `clustering.adjusted_rand_index` | assignment, reference label | maximize | `business` |
160
+ | `clustering.normalized_mutual_info` | assignment, reference label | maximize | `business` |
161
+
162
+ For feature-based metrics, `business` uses the raw FeatureSet values from the
163
+ DatasetArtifact. `transformed` uses the model's own train-fitted preprocessing
164
+ (the plugin's `transform`), which is the space the estimator clustered in and
165
+ is identical to `business` when the model has no preprocessing. Prefer
166
+ `transformed` for gates on models trained with a scaler or PCA, and record the
167
+ chosen space. `cluster_count` and `min_cluster_share` describe only the
168
+ non-empty clusters observed in the evaluated rowset. A trained cluster that
169
+ receives no rows there is not counted.
170
+
171
+ Example `evaluation-request.json`; `targets` is exactly the assignment output:
172
+
173
+ ```json
174
+ {
175
+ "model_artifact_id": "replace_clustering_artifact_id",
176
+ "dataset": {
177
+ "project": "default",
178
+ "dataset_id": "replace_dataset_id",
179
+ "manifest_hash": "replace_with_exact_artifact_manifest_hash"
180
+ },
181
+ "rowset": "test",
182
+ "targets": ["cluster_id"],
183
+ "config": {"name": "process_regimes_release", "version": "v1"},
184
+ "execution": {"executor": "kubernetes", "resources": {"cpu": "2", "memory": "4Gi"}}
185
+ }
186
+ ```
187
+
188
+ The rowset comes from the original TrainingRun's `data.splits`. Prefer a
189
+ holdout pool (`validation` or `test`). When the run declared no splits, the
190
+ only accepted rowset is `all`, which is the complete training population.
191
+ Evaluating `all` or `train` measures the population that fitted the model.
192
+ Label any such Result as in-sample in every report, and do not present it as
193
+ held-out evidence; the platform does not add that label for you.
194
+
195
+ Decision semantics follow the shared release policy. Exploratory mode is
196
+ always `INCONCLUSIVE`. In release mode:
197
+
198
+ - When a metric cannot be computed meaningfully (fewer than two non-empty
199
+ clusters, no more rows than clusters, zero dispersion, non-finite inputs),
200
+ the platform records reason `CLUSTERING_DEGENERATE` and writes no number.
201
+ - When a `transformed` metric is requested but the model runtime has no
202
+ `transform`, the platform records `CLUSTERING_TRANSFORM_UNAVAILABLE`.
203
+ - A required rule on such a missing metric is `INCONCLUSIVE`, and that
204
+ outranks a `FAIL` in another rule, so the decision is `INCONCLUSIVE`.
205
+ - Any configured metric that is missing makes `evidence_status` `INCOMPLETE`.
206
+
207
+ Fix the population or configuration rather than dropping the metric to get a
208
+ PASS.
209
+
210
+ Each Result stores `clustering_diagnostics.json` beside the standard artifacts.
211
+ It records the assignment and score outputs, feature order and hash, distance
212
+ metric, sampling parameters, whether the transformed space was available,
213
+ cluster sizes per slice, and the status of each metric (`computed`,
214
+ `degenerate` or `unavailable`, with sampled-row counts). The prediction
215
+ artifact carries the assignment and score per row key. List them with
216
+ `get-evaluation-result-artifacts <result-id>` and download sha256-verified
217
+ copies with `download-evaluation-result-artifact <result-id> --out-dir DIR
218
+ [--file NAME]`.
219
+
220
+ Registration requires all of the following:
221
+
222
+ - one Result for the same artifact with `decision=PASS`,
223
+ `evidence_status=COMPLETE` and compatibility `ok`;
224
+ - no required summary-level rule;
225
+ - a model runtime that reports `production_ready=true`.
226
+
227
+ ```bash
228
+ ml-platform --profile server register-model-artifact <artifact-id> \
229
+ --evaluation-id <evaluation-result-id>
230
+ ```
231
+
232
+ Training feedback (`cluster_count`, `min_cluster_share`,
233
+ `assignment_score_mean` in `metrics.json`) is not registration evidence.
234
+
103
235
  ## Diagnose before another build or training run
104
236
 
105
237
  | Symptom | Actual check and next action |
@@ -199,6 +331,7 @@ An EvaluationRunRequest binds ModelArtifact(s), a frozen DatasetArtifact rowset,
199
331
  ml-platform --profile server get-evaluation-result <result-id>
200
332
  ml-platform --profile server get-evaluation-result-metrics <result-id>
201
333
  ml-platform --profile server get-evaluation-result-artifacts <result-id>
334
+ ml-platform --profile server download-evaluation-result-artifact <result-id> --out-dir /tmp/eval-<result-id> --file predictions.parquet
202
335
  ml-platform --profile server summarize-evaluation-run <run-id>
203
336
  ml-platform --profile server get-evaluation-summary <summary-id>
204
337
  ```
@@ -159,3 +159,49 @@ ml-platform --profile server cancel-model-package <package-id> --package-version
159
159
  ```
160
160
 
161
161
  Cancellation requires exact-package authorization. Do not rebuild an immutable failed version; correct inputs and create a new semantic package version. This Skill does not deploy images or write model-center delivery status.
162
+
163
+ ## Clustering packages and requests
164
+
165
+ A clustering ModelArtifact has `output_bindings` and no `target_bindings`. Its
166
+ Serving contract reports `task_type=clustering`, `horizon_mode=null`, no
167
+ supported horizons, and `output_schema.kind=clustering` with the `cluster_id`
168
+ assignment and one score (`distance_to_center` or `cluster_probability`).
169
+
170
+ Package it with the ordinary request shape and `"input_modes": ["inline"]`.
171
+ Add `feature_lookup` only when the generic retrieval conditions above hold. A
172
+ clustering `feature_lookup_test` must omit `horizon` and `output_window`.
173
+ The platform-generated image smoke test sends exactly one inline row, in
174
+ ModelSignature column order, with no horizon. It requires one successful
175
+ result with `prediction.kind=clustering`, an integer `cluster_id` >= 0 and a
176
+ finite score.
177
+
178
+ Packaging includes every model file the artifact declares, including the v2
179
+ preprocessing sidecar. The Serving image must provide the scikit-learn version
180
+ recorded in a v2 model file, otherwise model loading fails and the package
181
+ cannot become `READY`. Use the clustering runtime binding from the same Runtime
182
+ Release rather than another image.
183
+
184
+ An inline request carries exactly one row, whose column names match the
185
+ ModelSignature order exactly. Its timestamp must not be later than
186
+ `cutoff_time`. Clustering requests reject `horizon` and `output_window`:
187
+
188
+ ```json
189
+ {
190
+ "input_mode": "inline",
191
+ "cutoff_time": "2026-01-01T08:00:00+08:00",
192
+ "inputs": {
193
+ "timestamps": ["2026-01-01T08:00:00+08:00"],
194
+ "values": [{"pressure": 0.82, "temperature": 1180.0, "flow": 3.4}]
195
+ }
196
+ }
197
+ ```
198
+
199
+ ```bash
200
+ ml-platform predict-model /absolute/path/to/cluster-request.json \
201
+ --serving-url https://process-regimes.example
202
+ ```
203
+
204
+ The single result returns `prediction.kind=clustering`, `cluster_id` and the
205
+ score field, and has no `horizon` or `target_time`. Report the `model_version`
206
+ (artifact ID) with each assignment, because cluster ids are not comparable
207
+ across artifacts.
@@ -112,6 +112,173 @@ capability requires it; do not attach it just because the task is a forecast.
112
112
  For the platform's detailed diagnostic and label-statistics procedure, read
113
113
  [`feature-management/references/supervised-datasets.md`](../../feature-management/references/supervised-datasets.md).
114
114
 
115
+ ## Unsupervised clustering runs
116
+
117
+ Use this method for tabular clustering SystemModels such as
118
+ `kmeans_clusterer:v1` and `gaussian_mixture_clusterer:v1`. Confirm that the
119
+ inspected SystemModel advertises `tasks=["clustering"]`; do not reuse a
120
+ supervised Trainer for clustering.
121
+
122
+ ```json
123
+ {
124
+ "schema_version": "ml_data_platform.training_run/v4",
125
+ "run_id": "process_regimes_kmeans_01",
126
+ "experiment": "process_regimes",
127
+ "task": {"kind": "clustering", "objective": "clustering"},
128
+ "data": {
129
+ "artifact": {
130
+ "project": "default", "dataset_id": "process_features",
131
+ "manifest_hash": "REPLACE_WITH_RETURNED_MANIFEST_HASH"
132
+ },
133
+ "features": {"feature_set": {
134
+ "project": "default", "name": "process_cluster_inputs", "version": "v1"
135
+ }},
136
+ "labels": [],
137
+ "splits": {
138
+ "assignment_column": "rowset_split", "train": ["training"],
139
+ "validation": ["validation"], "test": ["test"]
140
+ }
141
+ },
142
+ "input_adapter": {"kind": "tabular"},
143
+ "label_transform": {"name": "identity", "version": "v1"},
144
+ "trainer": {
145
+ "project": "default", "name": "kmeans_clusterer", "version": "v1",
146
+ "parameters": {
147
+ "n_clusters": 4, "n_init": 20, "random_state": 42,
148
+ "preprocessing": {"scaler": "robust", "pca_variance": 0.95}
149
+ }
150
+ },
151
+ "reproducibility": {"seed": 42, "deterministic": true}
152
+ }
153
+ ```
154
+
155
+ Validation enforces these rules:
156
+
157
+ - `task.objective` is `clustering`, `data.labels` is empty, `input_adapter.kind`
158
+ is `tabular`, and `label_transform` is `identity:v1`. Do not declare
159
+ `data.temporal`, `forecast_target_adapter` or label materializations for the
160
+ model; clustering has no supervised target.
161
+ - The model FeatureSet is non-empty. Every column is a numeric
162
+ `role=feature` column available `at_prediction` or `static`, and every
163
+ artifact row's value is finite and non-null. Correct data gaps in the
164
+ DatasetArtifact; the trainer does not impute.
165
+ - `data.splits` is optional. Without it, every artifact row is a training row.
166
+ With it, the cluster count must be between 2 and the train-pool row count.
167
+ Validation and test pools may be empty. Splits matter for preprocessing,
168
+ because transforms are fitted on train rows only.
169
+ - `trainer.parameters` is strict. Unknown keys, booleans in integer fields,
170
+ integral floats such as `4.0`, and out-of-range values are rejected before
171
+ scheduling. Read the authoritative schema with
172
+ `get-system-model kmeans_clusterer v1` or
173
+ `get-system-model gaussian_mixture_clusterer v1`.
174
+
175
+ | SystemModel | Cluster count (default) | Other estimator fields (defaults) |
176
+ | --- | --- | --- |
177
+ | `kmeans_clusterer:v1` | `n_clusters` >= 2 (8) | `init` `k-means++`/`random`; `n_init` >= 1 or `auto` (`auto`); `max_iter` >= 1 (300); `tol` >= 0 (1e-4) |
178
+ | `gaussian_mixture_clusterer:v1` | `n_components` >= 2 (2) | `covariance_type` `full`/`tied`/`diag`/`spherical` (`full`); `init_params` `kmeans`/`k-means++`/`random`/`random_from_data` (`kmeans`); `n_init` >= 1 (1); `max_iter` >= 1 (100); `tol` >= 0 (1e-3); `reg_covar` >= 0 (1e-6) |
179
+
180
+ Both models also accept `random_state` (0..2^32-1; when it is unset, the
181
+ Worker uses the run's `reproducibility.seed`, which it injects as `seed` at
182
+ run time and which overrides any `seed` in the parameters) and an optional
183
+ `preprocessing` object with exactly two fields:
184
+
185
+ - `scaler`: `"robust"` or `null`. A robust scaler centers each column on its
186
+ train-pool median and divides by its interquartile range.
187
+ - `pca_variance`: a float strictly between 0 and 1, or `null`. PCA keeps the
188
+ fewest components that explain that fraction of train-pool variance and runs
189
+ after the scaler.
190
+
191
+ The trainer fits both transforms on train rows only and freezes them in the
192
+ artifact. Validation, test, evaluation and Serving rows are transformed and
193
+ assigned without refitting. Clustering distances and scores are computed in
194
+ this transformed space.
195
+
196
+ Apart from that runtime `seed`, clustering parameters are not filled from
197
+ top-level `reproducibility`.
198
+ `effective_trainer_parameters` contains exactly the submitted keys, and omitted
199
+ fields use the schema defaults inside the Worker. Set the cluster count,
200
+ `random_state` and `n_init` explicitly when the run must be reproducible and
201
+ reviewable. Changing any of them requires a new `run_id`.
202
+
203
+ ### Choosing the cluster count and preprocessing
204
+
205
+ The defaults are silent. If the Run omits the cluster count, KMeans trains 8
206
+ clusters and Gaussian Mixture 2; if it omits `preprocessing`, no scaling or PCA
207
+ is applied. Validation does not warn, and `effective_trainer_parameters` echoes
208
+ only the submitted keys, so the default does not show up in the evidence you
209
+ review. Never submit a clustering Run that relies on either default.
210
+
211
+ Cluster count. Ask the user whether the business has a fixed number of regimes
212
+ or categories. If it does, use that number and record who confirmed it. If it
213
+ does not, select K with a documented method before treating any Run as final:
214
+
215
+ 1. Agree a candidate range with the user (for example 2–10), bounded by how
216
+ many clusters the business can name and act on and by the train-pool size.
217
+ 2. Fix everything except K: the same DatasetArtifact, model FeatureSet,
218
+ `preprocessing`, `n_init` and `random_state`. Each K is a separate
219
+ TrainingRun with its own `run_id`. Get authorization for the whole scan,
220
+ and follow [model-comparison.md](model-comparison.md) for recording
221
+ candidates, failures and stopping.
222
+ 3. Evaluate every candidate with one shared clustering EvaluationConfig
223
+ ([evaluation.md](evaluation.md)): `clustering.silhouette` (higher is
224
+ better), `clustering.davies_bouldin` (lower is better),
225
+ `clustering.calinski_harabasz` and `clustering.min_cluster_share`. Compare
226
+ transformed-space metrics only between runs with identical preprocessing;
227
+ use `calculation_space: "business"` when the comparison spans different
228
+ preprocessing.
229
+ 4. Check stability: retrain the leading candidates with two or three other
230
+ `random_state` values (or on subsamples) and compare the assignments, for
231
+ example with adjusted Rand index. Compute this in the workspace and label it
232
+ exploratory; it is not a governed EvaluationResult. A K whose clusters
233
+ change between seeds is not a stable business vocabulary.
234
+ 5. Show the user the leading candidates with cluster sizes and per-cluster
235
+ feature profiles in business units, and let the business judge whether the
236
+ clusters can be named and acted on. Metrics rarely give one clear winner,
237
+ so the final K is a business decision backed by the evidence above. Record
238
+ the range, metrics, stability result and decision.
239
+
240
+ Preprocessing. Ask, or justify from the data, instead of accepting the
241
+ default:
242
+
243
+ - `scaler`: KMeans and Gaussian Mixture are distance-based, so a column with a
244
+ large numeric range dominates. Use `"robust"` when columns have different
245
+ units or scales, or contain outliers. Use `null` only when every column
246
+ shares one unit and absolute differences are meaningful to the business
247
+ (for example, a set of temperatures where a 10-degree gap means the same
248
+ thing in every column), and record that reason.
249
+ - `pca_variance`: use it when many input columns are strongly correlated and
250
+ would otherwise count the same signal several times. A higher fraction
251
+ (0.95–0.99) keeps more detail; a lower one removes more noise. PCA makes
252
+ clusters harder to explain in business terms, so leave it `null` unless the
253
+ correlation is shown and the user accepts the trade-off.
254
+
255
+ Write the chosen K and `preprocessing` explicitly in `trainer.parameters`, even
256
+ when they equal the defaults.
257
+
258
+ A successful clustering Job writes training feedback (`cluster_count`,
259
+ `min_cluster_share`, `assignment_score_mean`) on the training `evaluation.split`,
260
+ which defaults to `auto`. This is diagnostic evidence only. The ModelSignature
261
+ outputs are `cluster_id` (int64) plus `distance_to_center` for KMeans or
262
+ `cluster_probability` for Gaussian Mixture. Cluster ids are meaningful only
263
+ within one immutable ModelArtifact.
264
+
265
+ The model file (`model.kmeans.json` or `model.gmm.json`) records its format:
266
+
267
+ - `<kind>_clustering/v1`: no preprocessing; estimator sidecar only.
268
+ - `<kind>_clustering/v2`: preprocessing is present. The file additionally
269
+ lists `transform_file` in `model_files`, records the training-time
270
+ `scikit_learn_version`, the preprocessing config, and the fitted summary:
271
+ scaler type, PCA component count, retained explained-variance ratio and
272
+ input feature order.
273
+
274
+ Loading refuses an unknown format, a missing estimator or transform sidecar,
275
+ or a v2 file whose recorded scikit-learn version differs from the runtime.
276
+ A v1 file records no scikit-learn version. Its safety relies on the training,
277
+ evaluation and Serving images of one Runtime Release sharing the same locked
278
+ scikit-learn stack, so keep v1 artifacts on images from that release line. Resolve a
279
+ version mismatch by using the Runtime Release binding whose image matches the
280
+ training runtime, or by retraining. Never edit artifact files.
281
+
115
282
  ## Chronos-2 LoRA parameters
116
283
 
117
284
  Chronos-2 fine-tuning settings belong under `trainer.parameters`; they are
@@ -227,7 +394,11 @@ weather observations.
227
394
 
228
395
  Inspect `data_schema.json`, `resolved_manifest.json` and the feature Parquet via
229
396
  `download-dataset-artifact DATASET_ID MANIFEST_HASH --project PROJECT --out-dir DIR`
230
- (repeat `--file` for selective downloads). In TrainingRun v4, the FeatureSet
397
+ (repeat `--file` for selective downloads). `MANIFEST_HASH` is the artifact
398
+ manifest hash from `list-dataset-artifacts` or `artifact.manifest_hash` of a
399
+ succeeded `build-registered-dataset --wait`, not the registered manifest hash.
400
+ After training, `download-model-artifact ARTIFACT_ID --out-dir DIR [--file NAME]`
401
+ returns sha256-verified ModelArtifact files such as `runtime.json` and the model file. In TrainingRun v4, the FeatureSet
231
402
  reference defines the ordered model inputs. It may be a registered exact-version
232
403
  subset of the DatasetArtifact's materialized FeatureSet, in its own order. The
233
404
  platform freezes both identities and the projected dependency contract in the