@zhuoyuezs/ml-platform 0.2.8 → 0.2.10

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 (36) hide show
  1. package/CI-ARTIFACT.md +2 -2
  2. package/checksums.json +74 -54
  3. package/package.json +1 -1
  4. package/release.json +19 -19
  5. package/runtime/business-client/README.md +64 -4
  6. package/runtime/business-client/package-lock.json +2 -2
  7. package/runtime/business-client/package.json +1 -1
  8. package/runtime/business-client/src/catalog.js +25 -6
  9. package/runtime/business-client/src/cli.js +177 -7
  10. package/runtime/business-client/src/http.js +192 -32
  11. package/skills/data-source-exploration/SKILL.md +12 -1
  12. package/skills/data-source-exploration/agents/openai.yaml +2 -2
  13. package/skills/data-source-exploration/references/data-platform.md +83 -0
  14. package/skills/feature-management/SKILL.md +29 -11
  15. package/skills/feature-management/agents/openai.yaml +2 -2
  16. package/skills/feature-management/references/commands.md +64 -7
  17. package/skills/feature-management/references/contracts.md +124 -6
  18. package/skills/feature-management/references/data-platform-mapping.md +104 -0
  19. package/skills/feature-management/references/operator-authoring.md +116 -8
  20. package/skills/feature-management/references/platform-capability-guide.md +6 -0
  21. package/skills/feature-management/references/supervised-datasets.md +13 -0
  22. package/skills/model-deployment-management/SKILL.md +7 -0
  23. package/skills/model-deployment-management/references/contracts.md +6 -0
  24. package/skills/model-deployment-management/references/operations.md +28 -1
  25. package/skills/model-deployment-management/references/troubleshooting.md +8 -0
  26. package/skills/model-lifecycle-management/SKILL.md +27 -5
  27. package/skills/model-lifecycle-management/agents/openai.yaml +2 -2
  28. package/skills/model-lifecycle-management/references/ablation.md +111 -0
  29. package/skills/model-lifecycle-management/references/discovery.md +18 -3
  30. package/skills/model-lifecycle-management/references/evaluation.md +188 -1
  31. package/skills/model-lifecycle-management/references/experiment-runs.md +59 -0
  32. package/skills/model-lifecycle-management/references/model-comparison.md +11 -9
  33. package/skills/model-lifecycle-management/references/packaging.md +46 -0
  34. package/skills/model-lifecycle-management/references/training-contracts.md +172 -1
  35. package/skills/model-lifecycle-management/references/training.md +60 -0
  36. package/skills/prediction-report-management/SKILL.md +43 -8
@@ -51,6 +51,43 @@ Inspect `get-metric-definition regression.mae v1` before relying on this metric.
51
51
  Release mode additionally requires at least one required validation rule with a
52
52
  business-approved threshold; do not invent thresholds to obtain PASS.
53
53
 
54
+ `regression.hit_rate:v1` is available for point-prediction regression metrics.
55
+ Set `parameters.tolerance` in the metric entry; tolerance is a finite,
56
+ nonnegative absolute error in the target's units, and equality counts as a hit:
57
+
58
+ ```json
59
+ {
60
+ "name": "regression.hit_rate", "version": "v1",
61
+ "targets": ["future_pressure"], "calculation_space": "business",
62
+ "parameters": {"tolerance": 5.0}
63
+ }
64
+ ```
65
+
66
+ The result is the fraction of valid rows whose absolute prediction error is at
67
+ most the tolerance. The resolved tolerance is recorded in each MetricResult and
68
+ must match when results are summarized or compared. It is an absolute threshold,
69
+ not a percentage; choose it using the target's unit and business acceptance
70
+ criteria.
71
+
72
+ To calculate per-day metrics on China Standard Time dates, add the time bucket
73
+ contract to the registered EvaluationConfig:
74
+
75
+ ```json
76
+ {
77
+ "time_buckets": {
78
+ "width": "1d",
79
+ "timezone": "Asia/Shanghai",
80
+ "alignment": "left"
81
+ }
82
+ }
83
+ ```
84
+
85
+ `timezone` accepts IANA timezone names and defaults to `UTC`. Daily buckets use
86
+ local calendar midnights and half-open intervals, so a bucket is keyed by the
87
+ local date; use `dimension: "time_bucket"` when creating an analysis view from
88
+ the resulting EvaluationResult IDs. The configured timezone is part of the
89
+ immutable EvaluationConfig identity.
90
+
54
91
  For a fixed-length multi-step forecast, `regression.mae` and
55
92
  `regression.rmse` cover only the terminal point at the maximum trained horizon.
56
93
  Use `regression.trajectory_mae` and `regression.trajectory_rmse` for a release
@@ -100,12 +137,144 @@ not prove Worker dependency availability. Validate before submitting and confirm
100
137
  the approved Worker supports the model plugin. Successful training in another
101
138
  image does not establish evaluation support.
102
139
 
140
+ ## Clustering evaluation
141
+
142
+ A clustering ModelArtifact (with `output_bindings`) is evaluated only by an
143
+ EvaluationConfig with `task_type: "clustering"`. Supervised configs reject
144
+ clustering artifacts, and clustering configs reject supervised artifacts.
145
+ There are no labels: the Worker loads the frozen model, predicts assignments
146
+ for the requested rowset, and computes metrics. It never refits the model or
147
+ its preprocessing and never relabels clusters.
148
+
149
+ Example release `evaluation-config.json`. The thresholds are placeholders and
150
+ must be business-approved before use:
151
+
152
+ ```json
153
+ {
154
+ "name": "process_regimes_release",
155
+ "version": "v1",
156
+ "task_type": "clustering",
157
+ "mode": "release",
158
+ "metrics": [
159
+ {"name": "clustering.silhouette", "calculation_space": "transformed"},
160
+ {"name": "clustering.davies_bouldin", "calculation_space": "transformed"},
161
+ {"name": "clustering.cluster_count"},
162
+ {"name": "clustering.min_cluster_share"}
163
+ ],
164
+ "validation": [
165
+ {"metric": "clustering.silhouette", "operator": ">=", "threshold": 0.25},
166
+ {"metric": "clustering.min_cluster_share", "operator": ">=", "threshold": 0.05}
167
+ ],
168
+ "clustering": {"sample_limit": 5000, "sample_seed": 0}
169
+ }
170
+ ```
171
+
172
+ The `clustering` object is optional and accepted only for `task_type:
173
+ "clustering"`:
174
+
175
+ - `sample_limit` (>= 2, default 5000) bounds the silhouette computation. Larger
176
+ rowsets are sampled deterministically with `sample_seed` (default 0). Other
177
+ metrics use every valid row.
178
+ - `reference_label_column` (optional) names an independent DataSchema column
179
+ with role `label` or `metadata`, never a model feature. It is required by the
180
+ external metrics `clustering.adjusted_rand_index` and
181
+ `clustering.normalized_mutual_info`, and is only a comparison reference,
182
+ never a training input.
183
+ - `distance_metric` currently accepts only `euclidean`.
184
+
185
+ Clustering configs reject `baseline`, comparison rules, sample weights and
186
+ package (code) metrics. Built-in `clustering.*` metrics and expression metrics
187
+ over them are supported:
188
+
189
+ | Metric | Inputs | Direction | Calculation spaces |
190
+ | --- | --- | --- | --- |
191
+ | `clustering.silhouette` | features, assignment | maximize | `business`, `transformed` |
192
+ | `clustering.davies_bouldin` | features, assignment | minimize | `business`, `transformed` |
193
+ | `clustering.calinski_harabasz` | features, assignment | maximize | `business`, `transformed` |
194
+ | `clustering.cluster_count` | assignment | neutral | `business` |
195
+ | `clustering.min_cluster_share` | assignment | maximize | `business` |
196
+ | `clustering.adjusted_rand_index` | assignment, reference label | maximize | `business` |
197
+ | `clustering.normalized_mutual_info` | assignment, reference label | maximize | `business` |
198
+
199
+ For feature-based metrics, `business` uses the raw FeatureSet values from the
200
+ DatasetArtifact. `transformed` uses the model's own train-fitted preprocessing
201
+ (the plugin's `transform`), which is the space the estimator clustered in and
202
+ is identical to `business` when the model has no preprocessing. Prefer
203
+ `transformed` for gates on models trained with a scaler or PCA, and record the
204
+ chosen space. `cluster_count` and `min_cluster_share` describe only the
205
+ non-empty clusters observed in the evaluated rowset. A trained cluster that
206
+ receives no rows there is not counted.
207
+
208
+ Example `evaluation-request.json`; `targets` is exactly the assignment output:
209
+
210
+ ```json
211
+ {
212
+ "model_artifact_id": "replace_clustering_artifact_id",
213
+ "dataset": {
214
+ "project": "default",
215
+ "dataset_id": "replace_dataset_id",
216
+ "manifest_hash": "replace_with_exact_artifact_manifest_hash"
217
+ },
218
+ "rowset": "test",
219
+ "targets": ["cluster_id"],
220
+ "config": {"name": "process_regimes_release", "version": "v1"},
221
+ "execution": {"executor": "kubernetes", "resources": {"cpu": "2", "memory": "4Gi"}}
222
+ }
223
+ ```
224
+
225
+ The rowset comes from the original TrainingRun's `data.splits`. Prefer a
226
+ holdout pool (`validation` or `test`). When the run declared no splits, the
227
+ only accepted rowset is `all`, which is the complete training population.
228
+ Evaluating `all` or `train` measures the population that fitted the model.
229
+ Label any such Result as in-sample in every report, and do not present it as
230
+ held-out evidence; the platform does not add that label for you.
231
+
232
+ Decision semantics follow the shared release policy. Exploratory mode is
233
+ always `INCONCLUSIVE`. In release mode:
234
+
235
+ - When a metric cannot be computed meaningfully (fewer than two non-empty
236
+ clusters, no more rows than clusters, zero dispersion, non-finite inputs),
237
+ the platform records reason `CLUSTERING_DEGENERATE` and writes no number.
238
+ - When a `transformed` metric is requested but the model runtime has no
239
+ `transform`, the platform records `CLUSTERING_TRANSFORM_UNAVAILABLE`.
240
+ - A required rule on such a missing metric is `INCONCLUSIVE`, and that
241
+ outranks a `FAIL` in another rule, so the decision is `INCONCLUSIVE`.
242
+ - Any configured metric that is missing makes `evidence_status` `INCOMPLETE`.
243
+
244
+ Fix the population or configuration rather than dropping the metric to get a
245
+ PASS.
246
+
247
+ Each Result stores `clustering_diagnostics.json` beside the standard artifacts.
248
+ It records the assignment and score outputs, feature order and hash, distance
249
+ metric, sampling parameters, whether the transformed space was available,
250
+ cluster sizes per slice, and the status of each metric (`computed`,
251
+ `degenerate` or `unavailable`, with sampled-row counts). The prediction
252
+ artifact carries the assignment and score per row key. List them with
253
+ `get-evaluation-result-artifacts <result-id>` and download sha256-verified
254
+ copies with `download-evaluation-result-artifact <result-id> --out-dir DIR
255
+ [--file NAME]`.
256
+
257
+ Registration requires all of the following:
258
+
259
+ - one Result for the same artifact with `decision=PASS`,
260
+ `evidence_status=COMPLETE` and compatibility `ok`;
261
+ - no required summary-level rule;
262
+ - a model runtime that reports `production_ready=true`.
263
+
264
+ ```bash
265
+ ml-platform --profile server register-model-artifact <artifact-id> \
266
+ --evaluation-id <evaluation-result-id>
267
+ ```
268
+
269
+ Training feedback (`cluster_count`, `min_cluster_share`,
270
+ `assignment_score_mean` in `metrics.json`) is not registration evidence.
271
+
103
272
  ## Diagnose before another build or training run
104
273
 
105
274
  | Symptom | Actual check and next action |
106
275
  | --- | --- |
107
276
  | `requires a named split` | Inherited TrainingRun `data.splits` is not a dictionary and requested rowset is not `all`. Inspect the original training request, not only manifest/resolved/Parquet splits. This check has no horizon-zero branch. |
108
- | `rowset is not declared` | Inspect `data.splits` role keys (`train`, `validation`, `test`), nonempty value arrays, assignment column and observed values. Manifest `training` can map to consumer role `train`. |
277
+ | `rowset is not declared` | Inspect `data.splits` role keys (`train`, `validation`, `test`). For an ordinary binding, check nonempty value arrays, assignment column and observed values. For `mode: derived`, check the timezone-aware `strategy: time_range` ranges and the resolved row counts. Manifest `training` can map to consumer role `train`. |
109
278
  | DataSchema mismatch | Compare the full schema with the trained Job's hash, including split columns, roles and dtypes. Do not remove columns or falsify hashes to pass. |
110
279
  | Label leakage | Materialize label columns separately from FeatureSet order; input selection alone cannot repair the contract. |
111
280
  | Chronos-2 runtime unavailable | Record executor, error stage/Job, runtime identity and Worker image evidence. The prediction plugin cannot load a dependency; request runtime support from the platform owner. New dataset/model versions do not install dependencies. |
@@ -183,6 +352,23 @@ ml-platform --profile server publish-executable-package <name> <version>
183
352
 
184
353
  Custom metric packages must be deterministic, digest-bound, network-restricted, verified, and separately authorized for publication.
185
354
 
355
+ The published wheel entrypoint is called as `entrypoint(inputs, context)` in an
356
+ isolated evaluation worker. `inputs` contains only the declared arrays (for
357
+ example `label`, `prediction`, `probability`, and `weight`). `context` contains
358
+ the frozen metric identity and the resolved parameters for this evaluation under
359
+ `context["parameters"]`; read that mapping instead of relying on package or
360
+ process globals. Parameter validation happens when the EvaluationConfig is
361
+ resolved, and the resolved values are persisted in the MetricResult and its
362
+ comparison/summary evidence. A package metric that declares no parameter schema
363
+ must reject supplied parameters.
364
+
365
+ ```python
366
+ def score(inputs, context):
367
+ tolerance = float(context["parameters"]["tolerance"])
368
+ error = abs(inputs["prediction"] - inputs["label"])
369
+ return float((error <= tolerance).mean())
370
+ ```
371
+
186
372
  ```bash
187
373
  ml-platform --profile server validate-evaluation-run evaluation-request.json
188
374
  ml-platform --profile server submit-evaluation evaluation-request.json
@@ -199,6 +385,7 @@ An EvaluationRunRequest binds ModelArtifact(s), a frozen DatasetArtifact rowset,
199
385
  ml-platform --profile server get-evaluation-result <result-id>
200
386
  ml-platform --profile server get-evaluation-result-metrics <result-id>
201
387
  ml-platform --profile server get-evaluation-result-artifacts <result-id>
388
+ ml-platform --profile server download-evaluation-result-artifact <result-id> --out-dir /tmp/eval-<result-id> --file predictions.parquet
202
389
  ml-platform --profile server summarize-evaluation-run <run-id>
203
390
  ml-platform --profile server get-evaluation-summary <summary-id>
204
391
  ```
@@ -0,0 +1,59 @@
1
+ # ExperimentRun
2
+
3
+ `ExperimentRun` is the platform control-plane record for a finite comparison
4
+ over one frozen `DatasetArtifact`. It supports model comparison, feature/input
5
+ ablation and a small candidate matrix while reusing existing TrainingRun,
6
+ TrainingJob, ModelArtifact, EvaluationRun, EvaluationResult and
7
+ EvaluationComparison records.
8
+
9
+ It does not implement AutoML, a general DAG engine, automatic model selection,
10
+ packaging or deployment. The Skill owns the modeling method and stopping rules;
11
+ the platform owns durable candidate state and reconciliation.
12
+
13
+ ## Lifecycle
14
+
15
+ Use the installed `ml-platform` CLI:
16
+
17
+ ```text
18
+ ml-platform create-experiment-run SPEC.json
19
+ ml-platform validate-experiment-run RUN_ID
20
+ ml-platform submit-experiment-run RUN_ID
21
+ ml-platform get-experiment-run RUN_ID
22
+ ml-platform list-experiment-candidates RUN_ID --limit 50 --offset 0
23
+ ml-platform reconcile-experiment-run RUN_ID
24
+ ml-platform cancel-experiment-run RUN_ID
25
+ ```
26
+
27
+ Create is idempotent for the same `experiment_run_id` and immutable
28
+ `spec_hash`. Always validate before submit. Validation resolves every candidate's
29
+ TrainingJob snapshot and has no training side effects. Submit is the mutation
30
+ that may create TrainingJob records, subject to `max_parallelism`.
31
+
32
+ ## Candidate contract
33
+
34
+ The spec must contain at least two candidates, a baseline candidate, one
35
+ DatasetArtifact identity and one evaluation contract. Each candidate carries a
36
+ complete TrainingRun v4 JSON object, a stable `candidate_id`, an optional
37
+ hypothesis and parent candidate, and an execution policy. Candidate FeatureSets
38
+ may be different exact-version subset/order projections of the same wide
39
+ DatasetArtifact. Labels, split membership, temporal semantics, evaluation
40
+ configuration and comparison population remain shared.
41
+
42
+ ## Recovery rules
43
+
44
+ `UNKNOWN` means the platform cannot prove whether a remote submission happened.
45
+ Reconcile first queries the durable TrainingJob/EvaluationRun records. It may
46
+ resume only when the lifecycle record proves no remote submission was reached;
47
+ it never blindly retries an unknown Kubernetes operation. An unknown candidate
48
+ also stops new candidate submissions until it is reconciled. A known terminal
49
+ failure is preserved and does not consume a parallel slot for independent
50
+ candidates.
51
+
52
+ ## Completion
53
+
54
+ Every candidate must be `RESULT_READY`, `FAILED`, `SKIPPED` or otherwise
55
+ accounted for. The service calls the existing EvaluationComparison implementation
56
+ only after complete results exist. `COMPLETE` with `ranking_allowed=true` requires
57
+ complete evidence, comparable population and a release-mode EvaluationConfig;
58
+ otherwise the run is `INCONCLUSIVE`. A result never authorizes registration,
59
+ packaging or deployment by itself.
@@ -1,10 +1,16 @@
1
1
  # Comparing models within one experiment
2
2
 
3
+ Feature or input ablation is the controlled one-factor variant of this
4
+ workflow. Read [ablation.md](ablation.md) for the baseline, removal-set and
5
+ round-closeout rules; the comparison gates below still apply.
6
+
3
7
  Use one documented goal and user-authorized scope: metric, target, candidate
4
8
  constraints and any maximum number of training submissions. Preserve that goal
5
9
  when a follow-up changes a constraint or asks for verification. Reuse existing
6
10
  authorization when it covers the next action; ask only for missing decisions.
7
11
  No TUI, session service, or Runtime-specific tool is required for this method.
12
+ Persist the candidate set and execution links in an `ExperimentRun`; see
13
+ [experiment-runs.md](experiment-runs.md) for its CLI lifecycle.
8
14
 
9
15
  ## Shared scope and candidate execution
10
16
 
@@ -45,9 +51,8 @@ forecast horizon, and do not let trajectory construction cross split
45
51
  boundaries. Compare complete platform-produced EvaluationResults only after
46
52
  each candidate passes its own TrainingRun and evaluation preflight.
47
53
 
48
- For each candidate, record its hypothesis, exact changed inputs, parent attempt
49
- if any, request JSON and returned immutable IDs in the agent's working
50
- directory. Submit within the user's authorization, observe the TrainingJob,
54
+ For each candidate, record its hypothesis, exact changed inputs and parent
55
+ candidate in the ExperimentRun spec. Submit within the user's authorization, observe the TrainingJob,
51
56
  validate the resulting ModelArtifact, then submit and inspect governed
52
57
  evaluation. Explain the observed result before choosing another candidate.
53
58
  Do not resubmit after an unknown outcome: reconcile the existing Job or retry
@@ -57,12 +62,9 @@ the authorization described in the training reference.
57
62
  ## Rounds, failure evidence and stopping
58
63
 
59
64
  A round groups candidates that test a related hypothesis under the frozen
60
- comparison scope. Keep a small round record in the agent's working directory
61
- (outside a platform source checkout), using normal file tools. Include the
62
- scope/request paths, candidate identities and changes, submission IDs, observed
63
- status, result IDs, per-candidate reflection and the round's next decision.
64
- This is an agent work record, not a new platform resource or required tool.
65
- Update it after submissions and observations so another coding agent can resume.
65
+ comparison scope. ExperimentRun and its candidates are the durable execution
66
+ record. Keep any additional method reflection in the agent's working directory
67
+ without duplicating platform status or IDs.
66
68
 
67
69
  When a candidate fails, inspect the actual Job status, attempts and diagnostic
68
70
  evidence. Distinguish a model execution failure from a runtime/infrastructure
@@ -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
@@ -15,6 +15,11 @@ FeatureSet order and evaluation runtime before the first Job. See
15
15
  do not try similar names or re-register trainers to bypass approval.
16
16
  - ARIMA supports the v1 single-target adapter for single-channel CPU
17
17
  forecasting; it does not support covariates or the v2 adapter.
18
+ - Clustering (`task.kind=clustering`) is unsupervised, CPU-only and tabular.
19
+ It uses TrainingRun v4 with `labels: []` and no temporal binding, optional
20
+ splits, and a strict parameter schema that includes train-fitted
21
+ `preprocessing`. Read the clustering section of
22
+ [training-contracts.md](training-contracts.md) before authoring the Run.
18
23
  - Chronos-2 supports the legacy v1 single-target adapter and the v2
19
24
  covariate-aware adapter. v2 jointly forecasts ordered target channels and
20
25
  accepts numeric `past_only` and `known_future` covariates. Do not silently
@@ -49,6 +54,40 @@ it does not reconstruct it from manifest `rowset_splits`. Save the submitted
49
54
  TrainingRun JSON, validation output, Job ID and ModelArtifact ID in the user's
50
55
  working directory so later diagnosis needs no source repository.
51
56
 
57
+ For a second experiment over the same immutable DatasetArtifact, a TrainingRun
58
+ may freeze a time based split without adding a split column to the artifact:
59
+
60
+ ```json
61
+ {
62
+ "splits": {
63
+ "mode": "derived",
64
+ "strategy": "time_range",
65
+ "source_rowsets": ["all"],
66
+ "ranges": {
67
+ "train": {"start": "2026-01-01T00:00:00+08:00", "end": "2026-07-01T00:00:00+08:00"},
68
+ "validation": {"start": "2026-07-01T00:00:00+08:00", "end": "2026-08-01T00:00:00+08:00"},
69
+ "test": {"start": "2026-08-01T00:00:00+08:00", "end": "2026-09-01T00:00:00+08:00"}
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ `time_range` uses timezone aware, left closed/right open prediction time
76
+ intervals and requires unique row keys. The platform freezes each role's row
77
+ identity in the resolved TrainingJob and rechecks it in the Worker. Samples
78
+ whose `label_time` reaches or passes their role's end are excluded and counted
79
+ in the validation snapshot; if that leaves no train samples, validation fails.
80
+ Training time evaluation must explicitly use `train` or `validation`; `test`
81
+ is reserved for an independent EvaluationRun. For `chronological_ratio`, provide
82
+ `ratios` with `train`, `validation` and `test` values summing to `1`; equal
83
+ prediction timestamps stay together. Actual counts may differ from requested
84
+ ratios, and the algorithm version and row identities are frozen in the Job
85
+ snapshot. Derived splits currently use all artifact rows as candidates; use
86
+ `assignment_column` for artifacts containing auxiliary history or truth rows.
87
+ Only `time_range` and `chronological_ratio` are supported. Confirm the selected
88
+ training and evaluation Worker images support derived splits before submission.
89
+ Existing `assignment_column` contracts keep their original behavior.
90
+
52
91
  ## Commands
53
92
 
54
93
  Read [training-contracts.md](training-contracts.md) when authoring a Run or Job
@@ -87,6 +126,27 @@ ml-platform --profile server cancel-training-job <job-id>
87
126
 
88
127
  Retry and cancellation require exact-Job authorization. Never change a run contract while retrying; create a new immutable run when semantic inputs change.
89
128
 
129
+ ## Clustering training sequence
130
+
131
+ Every step below is a separate gate. Registration and submission still need
132
+ explicit authorization.
133
+
134
+ ```bash
135
+ ml-platform --profile server get-system-model kmeans_clusterer v1 --project default
136
+ ml-platform --profile server validate-training-run cluster-run.json
137
+ ml-platform --profile server register-training-run cluster-run.json
138
+ ml-platform --profile server validate-training-job cluster-job-request.json
139
+ ml-platform --profile server submit-training-job cluster-job-request.json
140
+ ml-platform --profile server get-training-job <job-id>
141
+ ml-platform --profile server validate-model-artifact <artifact-id>
142
+ ```
143
+
144
+ The Job request uses `"device": "cpu"`; clustering SystemModels advertise no
145
+ other device. Before submission, confirm that `effective_trainer_parameters`
146
+ equals the submitted `trainer.parameters`. After success, validate the exact
147
+ `status.artifact_id`; the artifact must carry clustering `output_bindings`
148
+ instead of `target_bindings`.
149
+
90
150
  ## Chronos-2 fine-tuning parameters
91
151
 
92
152
  Configure Chronos-2 algorithm parameters only under