The workflow is always: Ingest → (Preview Dataset) → Extract Features → (Check Hparams) → Train → Monitor → Browse/Manage → (Profiles)
POST http://localhost:8000/v1/ingest
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | ✅ | Your .parquet file |
config |
text | ❌ | Optional JSON config string |
Postman setup:
- Body →
form-data - Key:
file→ type: File → select your.parquet - Key:
config→ type: Text → paste the JSON below (or omit for defaults)
Config (optional):
{
"timestamp_col": "timestamp",
"timestamp_format": "auto",
"forward_fill_limit": 5,
"backward_fill_first_rows": true,
"drop_duplicate_timestamps": true
}Response (200):
{
"dataset_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", ← SAVE THIS
"row_count": 10420,
"col_count": 134,
"tags_detected": ["12446", "12447", "12448"],
"quality_report": { ... },
"range_metadata": { "12446": {"min": 0.12, "max": 148.7}, ... },
"profile_id": 1, ← auto-resolved Tag Profile
"profile_name": "profile_a3f92c11", ← auto-named (renameable)
"tag_hash": "a3f92c11d4e7...", ← deterministic 64-char hash
"file_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"is_duplicate": false, ← true if file was previously ingested
"created_at": "2026-05-12T09:30:00Z"
}Important
Copy the dataset_id from the response — you need it for every subsequent call.
The profile_id is automatically resolved from your tag collection — datasets with the same tags always map to the same profile.
Deduplication: If you upload the exact same file twice, the system skips processing and returns is_duplicate: true with the original dataset_id.
Inspect the first N rows and column schema of any ingested dataset — useful for remote Flutter/mobile clients.
GET http://localhost:8000/v1/dataset/{dataset_id}/preview
GET http://localhost:8000/v1/dataset/{dataset_id}/preview?limit=50
| Query Param | Type | Default | Description |
|---|---|---|---|
limit |
int | 100 |
Max rows to return (1 – 1000) |
Response (200):
{
"dataset_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"original_filename": "sensor_data.parquet",
"row_count": 10420,
"col_count": 134,
"columns": ["timestamp", "tag_100_raw", "tag_100_roll_mean", "will_fail", ...],
"rows": [
{ "timestamp": "2026-01-01T00:00:00", "tag_100_raw": 51.23, "will_fail": 0, ... },
{ "timestamp": "2026-01-01T00:01:00", "tag_100_raw": 51.87, "will_fail": 0, ... }
]
}Tip
Use ?limit=5 for a fast schema inspection without transferring large payloads.
This tells you what tags and features exist in your dataset.
POST http://localhost:8000/v1/features/extract
Content-Type: application/json
Payload:
{
"dataset_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}Response (200):
{
"feature_schema_id": "a1b2c3d4-...", ← SAVE THIS
"dataset_id": "f47ac10b-...",
"tags": ["100", "200"], ← available tag IDs
"per_tag_features": {
"mandatory": ["raw", "roc_1", "roll_mean", "roll_std"],
"optional": [ ← these are what you can pick from
"custom_feature_1",
"custom_feature_2",
"dist_to_max",
"my_special_metric",
"pct_range"
]
},
"cross_tag_features": {
"available": ["system_avg_pct", "system_max_pct", "system_tags_high"],
"present_in_data": ["system_avg_pct"]
},
"target_col": "will_fail",
"target_present": true,
"total_columns": 17
}Tip
Read this response carefully! The tags, optional features, and target_col are what you use to build your training payload. Only pick tags and optional features that appear here.
GET http://localhost:8000/v1/hparams/{use_case}
Available use cases:
| Use Case | Model Type | Requires Training |
|---|---|---|
failure_prediction |
xgboost_clf | ✅ |
risk_scoring |
xgboost_clf | ✅ |
rul |
xgboost_reg | ✅ |
next_interval |
xgboost_reg | ✅ |
kpi_prediction |
xgboost_reg | ✅ |
anomaly_multivariate |
isolation_forest | ✅ |
anomaly_univariate |
statistical | ❌ |
adaptive_threshold |
statistical | ❌ |
early_warning |
statistical | ❌ |
health_index |
statistical | ❌ |
drift_detection |
statistical | ❌ |
pattern_detection |
statistical | ❌ |
data_quality |
statistical | ❌ |
Example:
GET http://localhost:8000/v1/hparams/failure_prediction
Response shows tier1/tier2/tier3 defaults with min/max ranges — you only need to override what you want to change.
POST http://localhost:8000/v1/train
Content-Type: application/json
tags— only use tag IDs from Step 2'stagsarraymandatory_features— optional override; defaults to auto-detected mandatory set (raw,roc_1,roll_mean,roll_std)optional_features— only use suffixes from Step 2'sper_tag_features.optionalarray; these are evaluated via combinatorial feature selection (exhaustive if ≤10 groups, greedy forward selection if more)feature_selection— set totrueto enable automated cross-validation based feature subset selection; defaults tofalsecross_tag_features— selectively specify cross-tag features from Step 2'scross_tag_features.availablearraytarget_col— required forxgboost_clfandxgboost_reguse cases; use the value from Step 2model_name— optional human-readable name for the saved.pklfile (e.g."My Sensor Model v2")hparams— only include keys you want to override; everything else uses defaults- Not every tag has every optional feature — the system handles this automatically (skips missing combinations)
{
"model_name": "Failure Model v1",
"dataset_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"feature_schema_id": "a1b2c3d4-e5f6-...",
"use_case": "failure_prediction",
"tags": ["100", "200"],
"mandatory_features": ["raw", "roc_1", "roll_mean", "roll_std"],
"optional_features": ["pct_range", "dist_to_max", "custom_feature_1"],
"feature_selection": true,
"cross_tag_features": ["system_avg_pct"],
"include_cross_tag_features": true,
"target_col": "will_fail",
"train_split": 0.8,
"cv_folds": 5,
"hparams": {
"n_estimators": 400,
"max_depth": 7,
"learning_rate": 0.05
}
}{
"dataset_id": "f47ac10b-...",
"feature_schema_id": "a1b2c3d4-...",
"use_case": "rul",
"tags": ["100", "200"],
"optional_features": ["pct_range"],
"feature_selection": true,
"include_cross_tag_features": false,
"target_col": "rul_hours",
"train_split": 0.8,
"cv_folds": 5,
"hparams": {
"n_estimators": 500,
"learning_rate": 0.03
}
}{
"dataset_id": "f47ac10b-...",
"feature_schema_id": "a1b2c3d4-...",
"use_case": "anomaly_multivariate",
"tags": ["100", "200"],
"optional_features": [],
"feature_selection": false,
"include_cross_tag_features": false,
"target_col": null,
"train_split": 0.8,
"cv_folds": 5,
"hparams": {
"contamination": 0.05
}
}Note
Anomaly detection does NOT need a target_col — set it to null.
{
"dataset_id": "f47ac10b-...",
"feature_schema_id": "a1b2c3d4-...",
"use_case": "failure_prediction",
"tags": ["100", "200"],
"target_col": "will_fail"
}Everything else (model_name, optional_features, feature_selection, include_cross_tag_features, train_split, cv_folds, hparams) defaults automatically.
Response (202 Accepted):
{
"model_id": "e8f9a0b1-...", ← SAVE THIS
"model_name": "Failure Model v1",
"job_id": "e8f9a0b1-...",
"status": "training",
"stream_url": "/v1/train/e8f9a0b1-.../stream",
"use_case": "failure_prediction",
"output_tag": "risk_score"
}Tip
The model is automatically linked to the Tag Profile resolved during ingest. Use GET /v1/profiles/{profile_id}/models to browse all versions trained on the same tag set.
GET http://localhost:8000/v1/train/{model_id}/stream
Accept: text/event-stream
Warning
Postman has limited SSE support. Use curl for real-time streaming:
curl -N http://localhost:8000/v1/train/{model_id}/streamThe stream emits one JSON log line per event as training progresses, then closes with a final result event containing the full metrics payload. The stream ends immediately after "Training complete" is logged — if you see it hanging after that message, check that the server process hasn't stalled.
Typical log sequence:
Training started
Dataset loaded
Feature selection started ← only if feature_selection=true and optional groups exist
Exhaustive search: N combinations ← or "Greedy forward selection" if >10 suffix groups
FeatureSelection combo 1/N fold 1/5
...
Feature selection complete
Model training complete
Evaluation complete
Artifact saved
Training complete ← stream ends here
GET http://localhost:8000/v1/models
GET http://localhost:8000/v1/models?use_case=failure_prediction
GET http://localhost:8000/v1/models?status=completed
GET http://localhost:8000/v1/models?use_case=rul&status=completed&limit=10&offset=0
GET http://localhost:8000/v1/models/{model_id}
GET http://localhost:8000/v1/models/{model_id}/download
Returns the .pkl file with the custom model_name as the suggested filename.
DELETE http://localhost:8000/v1/models/{model_id}
Caution
Delete removes both the DB record AND the .pkl / _meta.json files from disk. This is irreversible.
Every completed model response from GET /v1/models and GET /v1/models/{model_id} now includes an imp sub-object inside metrics. This is computed at read-time and is never stored in the database — it is always current relative to all peer models.
{
"metrics": {
"r2": 0.87,
"rmse": 0.42,
"mae": 0.31,
"cv_rmse_mean": 0.45,
"cv_rmse_std": 0.03,
"train_metrics": { "r2": 0.94, "rmse": 0.28, "mae": 0.22 },
"test_metrics": { "r2": 0.87, "rmse": 0.42, "mae": 0.31 },
"generalization_metrics": {
"overfit_gap_r2": 0.07,
"is_overfit": false,
"is_underfit": false,
"has_generalization_failure": false
},
"dataset_stats": {
"train_samples": 8000,
"test_samples": 2000,
"target_unique_values": 412
},
"imp": {
"primary": { "metric": "r2", "value": 0.87, "direction": "higher_is_better" },
"secondary": { "metric": "cv_rmse_mean", "value": 0.45, "direction": "lower_is_better" },
"tertiary": { "metric": "mae", "value": 0.31, "direction": "lower_is_better" },
"ranking": {
"local_rank_score": 83.33,
"leaderboard_position": 1,
"total_competitors": 4,
"ranking_available": true,
"ranking_reason": null,
"ranking_scope": "within_use_case",
"ranking_group": "regression"
},
"deployment_health": {
"deployment_health_score": 74.21,
"confidence_level": "high",
"deployment_readiness": "good",
"score_breakdown": {
"predictive_power": 32.5,
"stability": 18.2,
"generalization": 14.1,
"penalties": 0.0
}
},
"model_fit_analysis": {
"is_underfit": false,
"is_overfit": false,
"has_generalization_failure": false
},
"schema_warnings": [],
"validation_checks": {
"target_schema_valid": true
}
}
}
}| Field | Description |
|---|---|
primary / secondary / tertiary |
The 3 most important metrics for this model family with their direction |
ranking.local_rank_score |
Percentile rank among peers in the same use case (0–100). null if fewer than 2 completed peers exist |
ranking.leaderboard_position |
Integer position (1 = best). null if ranking unavailable |
ranking.total_competitors |
Number of completed models in the same ranking group |
ranking.ranking_available |
false when fewer than 2 peers exist |
ranking.ranking_group |
One of "classification", "regression", "anomaly_detection" |
deployment_health.deployment_health_score |
Penalized quality score [0–100] after applying overfit/stability penalties |
deployment_health.confidence_level |
"low" / "medium" / "high" |
deployment_health.deployment_readiness |
"unsafe" / "weak" / "experimental" / "good" / "production_ready" |
deployment_health.score_breakdown |
Component contributions to the score |
model_fit_analysis.is_underfit |
Model performs poorly on both train and test |
model_fit_analysis.is_overfit |
Large train-test performance gap detected |
model_fit_analysis.has_generalization_failure |
Extreme train-test divergence — model should not be deployed |
| Model Family | Primary | Secondary | Tertiary |
|---|---|---|---|
xgboost_clf |
auc_roc ↑ |
f1 ↑ |
cv_auc_std ↓ |
xgboost_reg |
r2 ↑ |
cv_rmse_mean ↓ |
mae ↓ |
isolation_forest |
p50_p5_gap ↑ |
score_std ↑ |
p95_p50_gap ↑ |
Note
↑ = higher is better, ↓ = lower is better.
Ranking is always computed relative to peers in the same use case at read-time. Adding or removing models from the DB immediately changes local_rank_score and leaderboard_position on the next GET request.
Every supervised model (xgboost_clf, xgboost_reg) now reports a generalization_metrics block inside metrics:
Regression:
"generalization_metrics": {
"overfit_gap_r2": 0.07,
"is_overfit": false,
"is_underfit": false,
"has_generalization_failure": false
}Classification:
"generalization_metrics": {
"overfit_gap_auc": 0.04,
"is_overfit": false,
"is_underfit": false,
"has_generalization_failure": false
}| Flag | Condition |
|---|---|
is_overfit (reg) |
train_r2 - test_r2 > 0.2 |
has_generalization_failure (reg) |
train_r2 - test_r2 > 0.5 |
is_underfit (reg) |
test_r2 < 0.0 |
is_overfit (clf) |
train_auc - test_auc > 0.15 |
has_generalization_failure (clf) |
train_auc - test_auc > 0.3 |
When feature_selection: true is set and at least one optional_features suffix group is specified, the trainer runs automated CV-based feature subset evaluation before the final fit:
- ≤ 10 suffix groups → exhaustive search (all 2^N subsets evaluated)
- > 10 suffix groups → true greedy forward selection — starts with the best single suffix, then at each step adds the suffix that most improves the primary CV metric, stopping when no remaining suffix yields further improvement
The best-performing column set is selected and used for all final training and evaluation. The log stream will show per-combo, per-fold metrics during this phase.
Note
The internal feature selection metric (score_std for Isolation Forest, rmse for regression, auc_roc for classification) is independent from the imp ranking metrics. The imp block always uses the test-set evaluation for leaderboard ranking regardless of what metric was used internally for feature selection.
When feature_selection: false (default), optional_features_used in the stored record is always [] — not the list of all available optional groups.
Every ingest auto-resolves a Tag Profile — a named group that maps a deterministic hash of integer tag IDs to datasets and models. Datasets ingested with the same tag collection always land in the same profile.
GET http://localhost:8000/v1/profiles
Response:
{
"items": [
{
"id": 1,
"profile_name": "profile_a3f92c11",
"tag_hash": "a3f92c11d4e7...",
"created_at": "...",
"updated_at": "...",
"model_count": 3
}
]
}GET http://localhost:8000/v1/profiles/{profile_id}
PATCH http://localhost:8000/v1/profiles/{profile_id}
Content-Type: application/json
{
"profile_name": "Engine Sensors Group A"
}GET http://localhost:8000/v1/profiles/{profile_id}/models
Returns all model artifacts ever trained on datasets belonging to this profile.
DELETE http://localhost:8000/v1/profiles/{profile_id}
Caution
Deleting a profile cascades to delete all associated model artifacts and their physical files. This is irreversible.
| Field | Type | Default | Notes |
|---|---|---|---|
dataset_id |
string | — | From Step 1 response |
feature_schema_id |
string | — | From Step 2 response |
model_name |
string | null |
Optional friendly name for .pkl file |
use_case |
string | — | See table in Step 3 |
tags |
string[] | — | Tag IDs from Step 2 tags array |
mandatory_features |
string[] | auto-detected | Override default mandatory feature set |
optional_features |
string[] | [] |
Suffixes from Step 2 optional array |
feature_selection |
bool | false |
Enable CV-based optional feature subset selection |
cross_tag_features |
string[] | [] |
Selectively include specific cross-tag features |
include_cross_tag_features |
bool | false |
Fallback flag to include all available cross-tag features |
target_col |
string | null |
Required for clf/reg, null for anomaly |
train_split |
float | 0.8 |
Train/test ratio (0.1 – 0.99) |
cv_folds |
int | 5 |
Cross-validation folds (2 – 20) |
hparams |
object | {} |
Only override what you need |
| Method | Endpoint | Description |
|---|---|---|
POST |
/v1/ingest |
Upload & ingest a Parquet file |
GET |
/v1/dataset/{dataset_id}/preview |
Preview dataset rows and schema |
POST |
/v1/features/extract |
Discover tags and feature schema |
GET |
/v1/hparams/{use_case} |
Get default hyperparameters |
POST |
/v1/train |
Spawn a training job |
GET |
/v1/train/{model_id}/stream |
SSE stream training logs |
GET |
/v1/models |
List model artifacts (filterable) |
GET |
/v1/models/{model_id} |
Get model details |
GET |
/v1/models/{model_id}/download |
Download .pkl file |
DELETE |
/v1/models/{model_id} |
Delete model + files |
GET |
/v1/profiles |
List all Tag Profiles |
GET |
/v1/profiles/{profile_id} |
Get profile details |
PATCH |
/v1/profiles/{profile_id} |
Rename a profile |
GET |
/v1/profiles/{profile_id}/models |
Models under a profile |
DELETE |
/v1/profiles/{profile_id} |
Delete profile + all its models |
GET |
/health |
Health check |