Skip to content

A Databricks project from source rows to predictions

Follow one project through data preparation, training, publication and monitoring. The running example predicts numeric claim_amount from income and age for 120 customers. It uses a single pandas model and whole-frame scoring on Databricks. Optional chapters explain how feature tables, serving and rollout attach to this same lifecycle. The glossary defines the terms; the troubleshooting guide starts from error messages.

Four finished projects built from this template on samples.nyctaxi.trips are in the NYC taxi examples: a single model with Spark scoring, a single model served online with feature lookups, a model competition, and a model set with combined scoring. Each has its setup answers, and one SQL file creates their tables. To copy the closest one instead of following this guide, see start from the closest example.

Commands run from your generated project unless a step says otherwise. Replace YOUR_PROFILE, workspace host and the approved catalog/schema with your own. Examples of expected output are expectations, not claims about your workspace. No input data is created by bundle init or deployment.

Stage Result to inspect Detailed reference
Prepare data CDF Delta table and unique record keys Feature tables and merges
Configure training Resolved source, split, models and recipes Every training field
Prepare features Fixed pre-split rules and fitted transformations Preprocessing catalog
Fit and decide Candidate version, protected holdout and decision Training reports, approve or roll back
Score Concrete model, source watermark and Delta receipt Scoring guide
Observe Saved drift/performance evidence and retraining decision Monitoring overview

The generated README.md is the short offline path. GUIDE.md holds the project's reference; both remain bound to that project's revision. The public site can describe a newer release; follow the upgrade chapter before changing runtime pins.

The chapters follow one path from a new project to monitored predictions:

flowchart LR
    A["Create project"] --> B["Load source data"]
    B --> C["Set training and recipes"]
    C --> D["Preview and deploy"]
    D --> E["Train and read report"]
    E --> F["Approve candidate"]
    F --> G["Score"]
    G --> H["Monitor"]

1. Check prerequisites

When you need it

Before creating resources or choosing a deployment identity.

What to edit

Use an existing workspace, catalog and schema you are allowed to use. The deployment identity needs rights to manage the generated jobs. The jobs' Run-as identity needs source reads, output/metadata writes, model registry and MLflow access, plus rights to start downstream jobs. Monitoring has its own store and reference permissions. Configure these in deployment/targets.yml and deployment/variables.yml; a successful local preview does not test them.

Use an authenticated Databricks CLI and a Core release matching the template. Generated jobs install deployment/requirements.txt; see runtime packaging.

The command

databricks version
databricks auth describe --profile YOUR_PROFILE

What you should see

The profile resolves to your intended workspace and identity. Avoid copying access tokens into project configuration.

Common errors and fixes

A different host or principal means the wrong profile is active. Fix authentication before deployment; do not treat local package installation as proof of cloud permissions.

2. Create the project

Detailed reference: Setup modes, generated files and preview.

When you need it

Once per new project. Quick and detailed setup generate the same project for the same answers.

What to edit

The answer file below selects single_model, regression and the source used in chapter 3. Quick setup is the default. Without an answer file, the wizard asks essential applicable questions; select detailed for model/candidate/branch, search, compute and deployment choices. Defaults remain editable in generated YAML. Competition compares candidates for one target; multi-target trains separate targets and packages one coherent model set.

The command

From the Skyulf checkout, save this as quick-init.json. Omitting setup_mode selects quick setup; the explicit answers avoid prompting:

{
  "project_name": "claim_demo",
  "catalog": "workspace",
  "schema": "skyulf_demo",
  "training_layout": "single_model",
  "task": "regression",
  "source_table_name": "customer_observations",
  "record_key_columns": "customer_id",
  "input_columns": "income, age",
  "target_column": "claim_amount"
}
databricks bundle init skyulf-core/templates/databricks --config-file quick-init.json --output-dir ./generated
cd generated/claim_demo
uv pip install -r deployment/train-requirements.txt

What you should see

A generated/claim_demo directory containing configuration, jobs, Python extensions and topic guides. No tables, models or endpoints are created by initialization.

Common errors and fixes

Run later commands from generated/claim_demo. Do not add a second workflow.json or Python model declaration beside the generated YAML owners.

3. Supply data, directly or through feature groups

Detailed reference: Feature tables and merge contracts.

When you need it

Before training or scoring can read real rows.

What to edit

For this tutorial keep config/features.yml at groups: {}. There is no feature job and no merged table: training and scoring read customer_observations directly. The catalog/schema must already exist. Create the example table once below, or use a table with the same schema and update the source name in both configurations.

If your inputs must first be assembled, follow the feature-table tutorial: each group reads its own source, calls a Spark transform and writes a feature table; the merge joins those tables onto the base observations. Then explicitly set the merged table as training_table and score_source_table. Feature groups do not change those sources automatically. This optional path is separate from fitted preprocessing and online feature lookup.

The command

This example predicts a numeric claim_amount from income and age, keyed by customer_id. Use an existing authorized Delta table with those columns, or create the following demonstration table in a schema you own. Run this SQL once in Databricks; substitute your catalog/schema in both the SQL and the bindings below. CREATE TABLE deliberately fails if the name is already in use.

CREATE TABLE workspace.skyulf_demo.customer_observations
USING DELTA
TBLPROPERTIES ('delta.enableChangeDataFeed' = 'true')
AS SELECT
  id AS customer_id,
  CAST(20000 + id * 350 AS DOUBLE) AS income,
  CAST(20 + id % 45 AS DOUBLE) AS age,
  CAST(50 + id * 7 + id % 3 AS DOUBLE) AS claim_amount
FROM range(120);

Expected: 120 unique BIGINT keys, two DOUBLE inputs and a DOUBLE label. This is an execution example, not a realistic business benchmark. Record keys identify requests; keep them out of input_columns unless they are genuine features. The rules for record keys, labels and source windows are in training data.

What you should see

The source has 120 distinct nonmissing BIGINT customer_id values. income, age and claim_amount are DOUBLE columns. Record keys identify outputs and stay outside model inputs.

Common errors and fixes

An existing table name makes CREATE TABLE fail deliberately. Choose a new owned name rather than replacing data. Enable CDF before new scoring changes arrive. A timestamp can be a feature-join key but is not a supported single-model prediction record key; use a stable STRING or BIGINT ID. See prepare the source table.

4. Choose training data, model and policy

Detailed reference: Training rows and holdout, model catalog, search strategies and decision thresholds.

When you need it

Before each new fit when changing data selection, evaluation, estimator or recipe.

What to edit

Edit config/training.yml. The generated table bindings and model entry already match the answer file. Merge this fragment into defaults; keep the other generated settings and the models mapping:

defaults:
  input_columns: [income, age]
  target_column: claim_amount
  pre_split_recipe: known_income
  preprocessing_recipe: claim_inputs
  save_preprocessing_diagnostics: true

The defaults use linear regression with a bounded search. Keep them for this run, or remove the model's tuning block to fit fixed parameters. The training field tables explain all accepted settings, types, defaults, inheritance and layout restrictions. Use that page's complete three-layout files when changing layout. For another algorithm, use the model catalog: each card has its parameters, default search space and YAML selection. The search and ensemble guide explains strategy controls, CV, budgets and how to configure voting or stacking members.

The command

There is no cloud action in this chapter. Complete the next two recipe files before running python src/tools/preview.py --action train in chapter 7.

What you should see

Training data flow

The job pins a Delta source version, selects the configured window and eligible sample, applies fixed training filters, creates the final split, and then fits preprocessing/model parameters inside training folds. Holdout rows stay outside search and fitting. Label availability filtering is independent of observation time and split policy.

Review training_window_mode, optional training_sample_rows, split_strategy and cv separately. Random splitting needs no event dates. Temporal splitting requires the configured time column and boundaries. Null training_table_version resolves latest once at invocation; a concrete version pins that snapshot. A rolling window changes at invocation, while a fixed window keeps its boundaries.

Three independent decisions

Setting Location Meaning
promotion_policy Training policy, or inference model_set policy manual_approval waits for an operator; automatic applies saved quality gates
score_model_selection config/inference.yml Resolve the controlled champion or use a pinned model_version
follow_champion config/inference.yml true starts scoring after a successful champion transition; false leaves scoring to its own trigger

Manual approval does not imply manual scoring. A pinned scorer keeps using its pinned version even after champion changes. Rejection and training without a champion transition do not trigger score handoff.

Common errors and fixes

Quick setup's manual policy leaves quality_threshold unset. This allows candidate registration and pinned scoring, but first-champion approval needs an absolute quality limit chosen before training. To exercise approval in this synthetic example, add quality_threshold: 5.0 under defaults; heldout RMSE must then be at most 5.0. Choose a meaningful business limit for real data. Editing it after the fit does not replace the candidate's saved policy.

5. Set fixed pre-split eligibility

Detailed reference: Custom pre-split filters and executed examples.

When you need it

When fixed cleanup or eligibility rules must run before holdout and CV splitting.

What to edit

Replace config/pre_split.yml with the complete file below. The previous chapter already selects known_income. It converts -999 to missing income and excludes those observations. It learns no means, quantiles or category vocabularies.

If another recipe filters missing claim_amount, set skip_target_pre_split_steps: true in config/scoring.yml before preview when you intend scoring to skip that label-dependent filter. Preview checks the decision even for training. This tutorial's income-only filter needs no such switch; actual labels are not required for prediction.

The command

For example, normalize a sentinel before testing training eligibility:

version: 1
recipes:
  default: []
  known_income:
    - name: income_sentinel
      transformer: ValueReplacement
      params: {columns: [income], to_replace: -999, value: null}
    - name: known_income
      transformer: DropMissingRows
      params: {subset: [income]}

Place this in config/pre_split.yml and select pre_split_recipe: known_income. Do not repeat the same normalization in preprocessing. With the generated mode: pre_split, scoring normalizes the sentinel, then excludes that row under known_income. The response retains its key with a missing prediction, scoring_status="excluded" and exclusion_reason="pre_split:known_income". The estimator receives only eligible rows. To predict for missing income, change the saved scoring eligibility/mode deliberately and include a fitted imputer, then train/package a new version; adding an imputer alone does not override this eligibility rule. Keep learned statistics after splitting.

What you should see

All 120 demo rows pass the filter. A future row with income -999 retains its key in scoring output, with no estimate and the explicit exclusion status described above.

Common errors and fixes

Keep learned imputers/scalers/encoders in preprocessing. Pre-split supports only the eight admitted built-ins and declared fixed filters. Deduplication does not repair duplicate record IDs and does not remember earlier scoring batches. Separate target-reading and feature-only filters if the latter must remain active at scoring time.

6. Set fitted preprocessing

When you need it

When inputs need learned filling, scaling, encoding or other model transformations.

What to edit

Replace config/preprocessing.yml with this complete file. claim_inputs is selected by chapter 4. The income filter is already in pre-split; this recipe fills missing age and scales the two numeric inputs.

version: 1
recipes:
  default: []
  claim_inputs:
    - name: age_fill
      transformer: SimpleImputer
      params: {columns: [age], strategy: median}
    - name: numeric_scale
      transformer: StandardScaler
      params: {columns: [income, age]}

The catalog gives each transform's parameters, defaults, minimal YAML, common mistake and saved-state behavior. Its customer classification example additionally covers group imputation and unseen categories.

The command

After both recipe files exist, inspect their resolved definitions:

python src/tools/preview.py --list-preprocessors

What you should see

The selected order is age imputation, then scaling. Preview resolves declarations; only training learns medians and scale statistics. Each CV fold fits its own state; holdout and prediction reuse saved state.

Common errors and fixes

Adding a YAML step does not alter an already registered model. Train a new version. Do not include record IDs or the target in scaler columns, and do not define the same phase in YAML and a Python builder.

7. Check locally and deploy

Detailed reference: Deployment identities, permissions and compute.

When you need it

After configuration changes and before any job run.

What to edit

Set workspace, namespace, identity and paused schedule bindings for the shared test target. Keep the package pin matched to this template.

The command

For this example use shared target test. In deployment/targets.yml, set its workspace.host to your workspace URL. In deployment/variables.yml, set test_catalog to workspace and test_input_schema, test_output_schema and test_metadata_schema to your approved skyulf_demo schema. The target root must be a directory your deployment identity controls. Replace YOUR_PROFILE below with the authenticated CLI profile for that workspace.

Install the generated training requirements in your local preview environment. There is no project-wheel or dist/ build step. Run:

uv pip install -r deployment/train-requirements.txt
python src/tools/smoke.py
python src/tools/preview.py
python src/tools/preview.py --action train
python src/tools/preview.py --action score

Use the local tools reference for preview's binding flags, defaults and scope; Local tools in the generated GUIDE.md keeps the same short reference beside the scripts. Then validate and deploy from the generated directory:

databricks bundle validate --strict -t test --profile YOUR_PROFILE
databricks bundle deploy -t test --profile YOUR_PROFILE

Choose manual schedules while learning the workflow. A scheduled job can start after deployment when its pause status is UNPAUSED.

What you should see

Smoke and train/score preview succeed. Strict validation resolves the resources. Deployment creates claim_demo_train, claim_demo_score and claim_demo_monitoring; it does not fit a model. No feature job appears with empty groups.

Common errors and fixes

Resolve every REPLACE_* value. A source field error belongs to configuration; a missing import belongs to the generated dependency files. Preview executes trusted project factories but does not read Delta rows. See exact-message fixes.

8. Train, inspect and decide

Detailed reference: Training reports, approve, reject and roll back, interrupted alias recovery.

When you need it

When you want a new candidate or an evidence-based activation decision.

What to edit

The reviewed YAML and source snapshot define this training run. Keep manual promotion for the first exercise; a registered candidate is usable by pinned scoring without becoming champion.

The command

databricks bundle run train -t test --profile YOUR_PROFILE

Open Workflows > train run > training_report > Output. The training reports guide explains the task graph, the notebook output and the saved input evidence. Chapter 4 turned on save_preprocessing_diagnostics; preprocessing diagnostics explains where its report is and how to read it.

The report lists the next actions with their exact parameters. Run them on the same train job; they reuse the saved candidate and do not fit a new model. For the first candidate of a new model, the expected champion is none. For example, only when the report actually returns candidate 1 and expected champion none:

databricks bundle run train -t test --profile YOUR_PROFILE --params lifecycle_action=approve,candidate_version=1,expected_champion_version=none

Operator actions explains every parameter, the comparison digest and the promotion receipt. To reject a candidate or undo a promotion, follow reject or roll back.

What you should see

The report shows the candidate version, protected holdout metrics, the quality decision and the next actions. After approval it shows the promotion receipt and the new champion. Keep that receipt for a later rollback. If score handoff is enabled, follow the child score run and inspect its publication separately; an approval result does not imply a score run occurred.

Common errors and fixes

A failed quality gate or a changed champion stops an approval. Inspect the current aliases and the new report before deciding again; do not start another fit only to approve. Copy concrete versions from the report; do not type an alias where a version is requested.

If a training task fails, open that task and its MLflow run. training_snapshot.json records the pinned source and the input, split and budget settings; training_pipeline_config.json records the original pipeline input and saved project source. Keep source Delta history and the recorded dependencies available for replay. A direct SDK replay rebuilds TrainingSpec from the snapshot and passes the original pipeline configuration to train_candidate. Do not substitute pipeline_config.json, which can already contain projected pre-split transformations. Restore the matching YAML and Python in a separate copy; do not overwrite newer project source to inspect a failed run.

9. Score and inspect published rows

When you need it

After a concrete candidate exists, and whenever new source rows are ready.

What to edit

In config/inference.yml, keep score_model_selection: pinned_version and set model_version to the actual result. The default is version "1". Redeploy changes before running. See scoring for the complete graph, business rules and layout differences.

The command

Score and inspect the demonstration rows

Quick setup defaults to score_model_selection: pinned_version and writes model_version: "1" into config/inference.yml. To score the registered result once, pass the version shown in training_report (<model> vN) as --params score_model_version=N; this changes neither the file nor any alias. For scheduled runs, set model_version to that version and redeploy. A pinned scorer can run a registered candidate without promoting it; choosing champion requires the successful activation described above.

databricks bundle run score -t test --profile YOUR_PROFILE

Open Jobs & Pipelines > claim_demo_score > latest run > score > Output. Inspect its concrete model version, source version, prediction table, counts and commit receipt. Default projects have no scoring_report task; that final report appears when optional CDF expiry recovery is enabled.

With the example's shared test bindings, inspect the named output:

SELECT COUNT(*) AS predictions,
       COUNT(DISTINCT customer_id) AS distinct_keys
FROM workspace.skyulf_demo.claim_demo_predictions;

SELECT * FROM workspace.skyulf_demo.claim_demo_predictions
ORDER BY customer_id LIMIT 5;

For the initial unchanged 120-row source, both counts should be 120. This checks publication, not holdout quality: the final holdout metrics come from training. Repeat score without changing the source; expect a no-op with the same 120 stored rows. For incremental acceptance, append new unique keys to the CDF source, run score again and verify that only those keys add rows.

What you should see

What happens to existing predictions?

Changing a model alias does not itself rewrite a prediction table. Score resolves one concrete model/set version and applies the configured change policy. Append mode preserves older rows and their provenance; a configured model-change full rebuild recomputes the current snapshot into a versioned generation and switches the stable prediction view only after success. The previous generation remains. Schema changes need a compatible destination. Model changes involving carried temporal history require rebuilding the corresponding history, not mixing states.

Aliases and results to inspect

Inspect the concrete registered candidate, champion, challenger, preserved previous aliases and promotion/rejection receipts. A registered version can exist after a later failure without having become champion. A scoring result should identify source watermark, model/version, row counts, no-op/rebuild outcome and Delta commit receipt. Match outputs by record keys.

Common errors and fixes

Recovery and schedules

After a scoring failure, inspect the target receipt before retrying. The writer can recognize an acknowledged or uncertain prior commit; deleting state or inventing a new watermark can duplicate work. Recovery options are explicit and do not convert arbitrary source updates/deletes into incremental inserts.

Train and score schedules are independent in deployment variables. A paused schedule prevents clock triggers, not already queued or manually started runs. Both jobs serialize their own active runs; protect registry and output ownership from outside writers too. A successful no-op score is different from a queued job.

Configure independent clocks and training data

Use training schedule settings for when to attempt fitting, scoring schedule settings for when to inspect new input, and training-window settings for which observations are eligible. The cron timezone does not define the source timestamp timezone. Preview rolling boundaries and result availability cutoffs before activating a schedule. See source windows for the detailed contract.

10. Observe and evaluate retraining

Detailed reference: Store setup, enrollment and references, delayed labels, drift, performance loss, retraining guards and dashboard interpretation.

When you need it

After scoring has committed, or when delayed labels make a later observation useful.

What to edit

Configure the shared monitoring catalog/schema and optional label source in deployment variables. Keep schedules paused until references are prepared. The monitoring overview covers store setup, reference preparation, label joins, drift/performance policies and the dashboard. A first pinned candidate that was never activated can need reference preparation after scoring enrollment.

The command

When an enrolled identity lacks its reference, prepare it explicitly:

databricks bundle run monitoring -t test --profile YOUR_PROFILE --params monitoring_action=prepare_references

Then retry the same score run to finish its monitoring handoff, or run the separate monitoring job with its ordinary observe action. Do not delete committed predictions.

What you should see

Inspect the separate monitoring run

Open the score run's monitor_model child link when monitoring is enabled. In the separate monitoring job, inspect monitor_model, monitoring_report and evaluate_retraining. The last task can legitimately decide not to retrain. An initial inventory needs the project's central store and reference setup; follow store setup before unpausing its schedule.

Without a label source, drift may be available while observed performance is unavailable. Without configured performance policies there is no policy decision. Dashboard refresh reads stored observations; it does not create measurements. Use model/version/context filters to inspect the newly enrolled identity.

Common errors and fixes

If scoring committed but the monitoring child failed, inspect both stages. Spark reference is not prepared; run prepare_spark_monitoring_reference first. means the reference setup above is missing, not that the predictions disappeared. Retries must retain the saved source/model identity and receipt. No labels means performance is unavailable. Dashboard refresh rereads stored observations and cannot create absent measurements. Retraining needs its explicit policy and freshness, coverage and cooldown guards; it still passes normal model-quality gates.

11. Optionally serve online and increase traffic

Detailed reference: Endpoint and SQL queries, online features, daily rollout and serving observations.

When you need it

Only when consumers need an HTTP/SQL endpoint rather than a Delta prediction table.

What to edit

Follow Serving requests in the project's GUIDE.md for concrete model selection and endpoint creation. Online feature lookup adds key-based retrieval; gradual rollout controls traffic between served versions. These are separate choices. Online publication syncs Delta features to the selected online store; it does not send prediction requests. Lakebase provisioning is explicit, not a side effect of selecting serving.

The command

Use the named setup/run commands in those guides after completing their configuration. Validate and deploy their generated resources with the same target:

databricks bundle validate --strict -t test --profile YOUR_PROFILE
databricks bundle deploy -t test --profile YOUR_PROFILE

Deployment alone is not a request test. Send the documented request only after the endpoint and, when used, online publication report ready.

What you should see

A request identifies the concrete served model and returns its prediction. The rollout controller advances traffic only with sufficient passing evidence; missing checks hold traffic. Final champion promotion follows the configured full-traffic observation period.

Common errors and fixes

Daily traffic rollout does not apply to batch, incremental Delta scoring or Spark worker inference. Missing/stale online features obey the selected lookup policy. Do not infer endpoint readiness from a successful training or publication job. This optional endpoint path is not executed by the baseline tutorial.

12. Upgrade an existing generated project

Detailed reference: Project upgrade and migration.

When you need it

When adopting a newer Core/template release.

What to edit

Follow the upgrade guide. It lists the release notes to read, the files to compare and the Core pin to change.

The command

uv pip install -r deployment/train-requirements.txt
python src/tools/smoke.py
python src/tools/preview.py --action train
python src/tools/preview.py --action score
databricks bundle validate --strict -t test --profile YOUR_PROFILE

Deploy to your test target, exercise the affected fit/load/score path, and review receipts before upgrading other targets.

What you should see

The new runtime, generated graph and saved artifact requirements agree. See what you should see.

Common errors and fixes

An old deployed graph and a new runtime fail a workflow-contract check. Deploy the matching files together; see the upgrade errors.