Configuração de model definition
Objetivo
Esta página documenta a forma útil do ModelDefinition para operação diária:
quais campos importam, quais combinações são aceitas e como configurar
estratégias Nixtla sem depender de leitura direta do código.
Papel no fluxo operacional
Um ModelDefinition é a política de treino, promoção e forecast para uma
população de séries derivada de um snapshot canônico. Ele não é o artefato
treinado. O artefato treinado é um ModelInstance, criado por série depois que
o workflow roda.
Na prática, a organização configura os ModelDefinitions que quer operar. Cada
definição responde a perguntas como:
- qual tipo de série será treinado;
- em qual granularidade temporal;
- quais regras validam o input e a projeção;
- quais features e estratégias participam do treino;
- qual métrica decide promoção;
- quando uma campeã ativa pode ser reaproveitada.
O campo que define a população de séries é series_projection_recipe. Por
exemplo, uma definição com target_series_kind=sku_store e
target_grain=daily governa a visão diária por SKU e loja produzida a partir do
snapshot. Quando o workflow é disparado para essa definição, ele projeta todas
as séries sku_store diárias deriváveis do snapshot, treina candidatas por
SeriesKey e promove uma campeã por série.
Hoje não existe uma tabela ou API de atribuição granular dizendo que uma série
individual usa uma definição e outra série usa outra. A seleção operacional é
feita pelo model_definition_id escolhido no trigger:
organization_id + model_definition_id + snapshot_id -> resolve a configuração ativa desse ModelDefinition -> aplica a series_projection_recipe -> processa todas as séries projetadas para esse snapshotSe duas definições da mesma organização apontarem para o mesmo
target_series_kind e target_grain, o sistema permite rodar ambas. Elas
produzem conjuntos independentes de ModelInstances sob model_definition_ids
diferentes. Isso só deve ser usado quando houver dois produtos ou políticas de
forecast realmente distintos para a mesma população de séries.
Configurações por série existem apenas como overrides de configuração
(series_overrides) depois que a série já pertence à população projetada pela
definição. Elas não são um mecanismo de roteamento granular para escolher quais
séries entram ou saem do workflow.
Estrutura principal
Os campos centrais de configuração são:
| Campo | Papel |
|---|---|
name | nome legível da definição |
horizon_spec | horizonte previsto |
evaluation_spec | política temporal usada para promoção |
validation_policies | políticas de validação do frame canônico agrupadas por fase |
series_projection_recipe | receita que deriva a série alvo treinável |
feature_recipe | preparo padrão de feature engineering, com override por estratégia/série quando configurado |
champion_reuse_policy | quando reaproveitar a campeã ativa |
evaluator_specs | métricas suportadas para promoção e tracking |
primary_metric | métrica usada para comparação principal |
promotion_thresholds | limites mínimos para promoção |
strategy_specs | catálogo habilitado de candidatas |
O endpoint PUT /v1/model-definitions/{id}/config aplica atualização parcial:
campos omitidos ou enviados como null preservam o valor atual. Campos enviados
com valor substituem aquele trecho da configuração; por exemplo,
"feature_recipe": {"steps": []} limpa a receita, enquanto omitir
feature_recipe mantém a receita existente.
Para configuração por cliente, use manifestos JSON versionados e aplique no ClickHouse:
just config-validate configs/clients/client-a.jsonjust config-apply configs/clients/client-a.jsonO manifesto de cliente continua sendo o caminho operacional para ativar
revisões. O treino não lê esse registro diretamente do adapter de ClickHouse:
control/modeling/config expõe o boundary ActiveConfigurationResolverPort.
No runtime de jobs, infra/jobs/bootstrap.py injeta um resolver backed por
configuration_revisions.
O manifesto também é o ponto onde a organização declara quais definições quer
manter ativas para operação. Cada entrada de model_definitions referencia uma
definição persistida e atualiza sua configuração efetiva. Depois do apply, os
triggers de treino e predição continuam escolhendo uma única definição por vez
via model_definition_id.
Na hora de treinar, avaliar e promover, o resolver monta uma configuração
congelada por SeriesKey e estratégia: começa pelo snapshot de produto da
ModelVersion, aplica revisões ativas de organização, depois de
ModelDefinition, depois de série, e produz fingerprints estáveis para política
de validação, receita de projeção, receita de features, parâmetros da estratégia,
política de seleção e configuração resolvida. Esses hashes e o payload resolvido
são gravados nas referências de treino e auditoria de promoção.
Na execução de forecast, a campeã ativa não reconsulta as revisões ativas para
decidir horizonte, features, parâmetros de estratégia, receita de projeção ou
família da biblioteca. O job seleciona as instâncias ativas que serão
forecastadas, reconstrói a configuração a partir de
TrainingReference.resolved_config_snapshot, valida o hash congelado antes de
chamar a estratégia e falha se uma instância ativa antiga não tiver esse
snapshot. Por padrão, as campeãs ativas podem pertencer a versões diferentes do
modelo; model_version continua existindo apenas como filtro explícito do
comando. A projeção canônica para série alvo é executada uma vez quando todas as
instâncias selecionadas compartilham a mesma receita congelada. Receitas de
projeção incompatíveis falham de forma clara por enquanto. Assim, uma mudança de
configuração depois do treino afeta novos treinos, não a execução de um artefato
já treinado. Cada ForecastRun completado também grava a referência de execução
usada: versão da instância ativa, estratégia, URI do artefato e os hashes da
configuração congelada. Se uma série falhar depois dessa referência ser
resolvida, o job persiste o ForecastRun como FAILED, sem pontos e com
failure_reason, salva os demais resultados da seleção e ainda falha a tarefa
para o orquestrador.
O config-apply ativa revisões separadas por config_kind:
validation_policy, series_projection_recipe, feature_recipe,
modeling_policy e champion_reuse_policy.
No workflow, series_projection_recipe é consumida antes do treino pela task
project_training_series. Essa task atualiza o DemandSnapshotManifest com os
artefatos projetados. train_model_candidates passa a carregar as partições por
series_id e aplicar feature_recipe por candidata.
O trigger não recebe lista de séries. O conjunto de séries vem do índice
projetado por project_training_series, que é consequência da receita resolvida
para aquele ModelDefinition e do conteúdo do snapshot.
O manifesto JSON também aceita series_overrides dentro de cada entrada de
model_definitions. Cada override informa o series_key compacto e um bloco
config; o apply grava essas mudanças como revisões ConfigScopeType.SERIES
na mesma tabela configuration_revisions. Na resolução efetiva, as revisões
são aplicadas em ordem determinística: organização, definição de modelo e série.
{ "$schema": "../../../schemas/config-manifest.schema.json", "organization_id": "01956c73-9c1f-7fb7-94b1-a2c5bc65d900", "created_by": "ops", "model_definitions": [ { "model_definition_id": "01956c73-9c1f-7fb7-94b1-a2c5bc65d910", "config": { "name": "retail weekly", "horizon_spec": { "periods": 8, "grain": "weekly", "step": 1 }, "validation_policies": { "ingestion": { "rule_set_key": "atreides-demand-canonical-v1", "schema_version": "1", "rules": [ { "rule_key": "required_columns" }, { "rule_key": "organization_scope" }, { "rule_key": "quantity_validity" }, { "rule_key": "temporal_validity" }, { "rule_key": "series_identity_validity" }, { "rule_key": "source_validity" }, { "rule_key": "chronological_uniqueness" }, { "rule_key": "uom_consistency" } ] }, "snapshot_materialization": { "rule_set_key": "atreides-demand-canonical-v1", "schema_version": "1", "rules": [ { "rule_key": "schema_version" }, { "rule_key": "manifest_row_count" }, { "rule_key": "manifest_series_count" } ] }, "training_preparation": { "rule_set_key": "atreides-demand-canonical-v1", "schema_version": "1", "rules": [ { "rule_key": "required_projection_dimensions" }, { "rule_key": "chronological_uniqueness" }, { "rule_key": "uom_consistency" } ] } }, "series_projection_recipe": { "recipe_key": "category-store-weekly-sum", "target_series_kind": "category_store", "target_grain": "weekly", "aggregation": "sum" }, "feature_recipe": { "steps": [ { "step_key": "target.lags.v1", "params": { "lags": [1, 4] } } ] } } } ]}O schema para IDEs fica em schemas/config-manifest.schema.json. Manifests de
configuração são JSON.
Regras que valem sempre
primary_metricprecisa existir emevaluator_specs;promotion_thresholdssó podem referenciar métricas declaradas;- precisa existir ao menos uma estratégia habilitada;
strategy_keyelibrary_familysão normalizados para slug;params_schema.execution.max_workers, quando informado, precisa ser inteiro positivo;evaluation_spec.strategyaceitaholdout_tail;validation_policiesaceitaingestion,snapshot_materialization,training_preparationeprediction_preparation; o validador rejeita regras em fases que não têm o contexto runtime necessário;- ingestão valida o payload carregado antes da canonicalização; as demais fases validam frames canônicos Atreides; regras não renomeiam, juntam, completam nem normalizam dados de cliente;
- somente as regras presentes na política resolvida para a fase são executadas;
- regras de manifesto e projeção, quando configuradas, recebem
paramstipados no runtime:schema_version,manifest_row_countemanifest_series_countvêm doDemandSnapshotManifest;required_projection_dimensionsvem daseries_projection_recipe; series_projection_recipe.target_grainprecisa bater comhorizon_spec.grain;feature_recipe.stepsrepresenta preparo reutilizável padrão; uma estratégia pode declararstrategy_specs[].feature_recipequando precisar de um frame diferente;- cada
feature_recipe.steps[]é produzido pela definição canônica do step, que carrega o schema de params e retorna uma chamada configurada e tipada; - revisões ativas podem sobrescrever partes da configuração de treino, mas o objeto resolvido e congelado é o que segue para feature execution, avaliação, promoção, artefatos e forecast;
- com
champion_reuse_policy.require_current_config=true, campeãs antigas só são refrescadas quando o hash da configuração resolvida e os hashes de componentes gravados no treino ainda batem com a configuração atual da série e estratégia; - cada chamada ao
DemandValidationCatalogrecebe um contexto tipado de fase: ingestão usaframeeorganization_id; materialização também carregaDemandSnapshotManifest; treino e predição carregam manifesto eseries_projection_recipe. A strategy da fase resolve os params finais da regra a partir da configuração declarada ou do contexto runtime tipado; - após a projeção, o dataset de treino precisa conter apenas séries da mesma
organização, mesmo
target_series_kinde mesmotarget_grainda receita de projeção; - quando
evaluation_spec.holdout_periods=null, a engine usahorizon_spec.periods; - a família e a estratégia escolhidas precisam estar registradas no catálogo de estratégias.
Famílias suportadas
library_family | Contrato |
|---|---|
nixtla-statsforecast | uma chamada de treino por série |
nixtla-mlforecast | uma chamada de treino por série |
Contrato de evaluation_spec
{ "strategy": "holdout_tail", "holdout_periods": null}Regras:
holdout_periodsprecisa ser> 0quando informado;- a promoção usa as métricas retornadas pela estratégia no treino do candidato final;
- o holdout temporal continua servindo como caminho de validação train/predict;
- se uma série não satisfizer o caminho atual ou falhar durante avaliação/treino, a engine falha o run inteiro, não promove campeões parciais e deixa o erro explícito para retry do job.
Contrato de série, features e campeã
series_projection_recipe define qual recorte de série este ModelDefinition
treina:
{ "recipe_key": "sku-store-daily-sum", "target_series_kind": "sku_store", "target_grain": "daily", "aggregation": "sum"}Hoje os valores de target_series_kind são padronizados no domínio e a
agregação suportada é sum. As dimensões esperadas são derivadas desse valor
pelo SeriesKind. Exemplos válidos incluem sku_store, sku_region,
sku_channel, category_store, category_region, channel_store,
channel_region, category, region e channel.
A projeção acontece sobre o contrato canônico de demanda, não sobre dados brutos
de cliente. Para treinar category_store, por exemplo, o frame canônico precisa
trazer category_id, category_system e category_key; a engine agrega as
observações por categoria, loja, período e UOM antes de criar o
SeriesTrainingContext.
feature_recipe é a área para passos reutilizáveis de preparo. Ela pode ficar
no nível do modelo como padrão ou em strategy_specs[].feature_recipe quando uma
estratégia precisar de outro frame para a mesma série:
{ "steps": [ { "step_key": "calendar.date_parts.v1", "params": { "columns": ["dayofweek"] } } ]}step_key identifica uma definição canônica de feature engineering. Essa
definição carrega o schema de params; ao receber o payload, ela retorna uma
chamada configurada e tipada para o executor. Esses passos são executados pelo
boundary de data-preparation antes de cada estratégia receber o frame por série.
Hoje existem implementações Spark e Polars para os mesmos step_key suportados;
um step_key desconhecido ou params malformados falham cedo.
Passos canônicos disponíveis:
target.lags.v1: cria defasagens da variável alvo, comoquantity_lag_1;target.rolling.v1: cria médias móveis por janela configurada e a média expansiva defasada (quantity_expanding_mean_lag_1);target.ewm.v1: cria média móvel exponencial defasada;calendar.date_parts.v1: cria partes de data comoperiod_week,period_month,period_quartere, quando solicitado,period_dayofweek;calendar.fourier.v1: cria termos sazonais seno/cosseno;calendar.br_holidays.v1: cria features de feriado brasileiro.
champion_reuse_policy controla refresh de campeãs:
{ "refresh_active_champion": true, "require_current_config": true}Com a política padrão, uma campeã ativa só é retreinada diretamente quando o
resolved_config_hash gravado no treino continua compatível com a configuração
atual da mesma série e estratégia. Se qualquer revisão efetiva mudar esse hash,
se a estratégia campeã é desabilitada, ou se refresh_active_champion=false, a
série volta para busca completa entre as estratégias habilitadas.
Contrato de strategy_specs
Cada item de strategy_specs tem esta forma:
{ "strategy_key": "seasonal-naive", "library_family": "nixtla-statsforecast", "enabled": true, "params_schema": { "model": { "name": "seasonal-naive", "hyperparams": {} }, "intervals": { "levels": [] }, "execution": { "max_workers": 1 } }}Campos de params_schema
| Campo | Tipo | Uso |
|---|---|---|
model.name | string | nome concreto do modelo dentro do adapter |
model.hyperparams | object | mapa livre de hiperparâmetros |
intervals.levels | integer[] | níveis de intervalo, como [80, 95] |
execution.max_workers | integer | paralelismo máximo entre séries independentes |
Exemplo mínimo para nixtla-statsforecast
{ "name": "mvp-seasonal-naive", "horizon_spec": { "periods": 7, "grain": "daily", "step": 1 }, "evaluation_spec": { "strategy": "holdout_tail", "holdout_periods": null }, "validation_policies": { "ingestion": { "rule_set_key": "atreides-demand-canonical-v1", "schema_version": "1", "rules": [ { "rule_key": "required_columns" }, { "rule_key": "organization_scope" }, { "rule_key": "quantity_validity" }, { "rule_key": "temporal_validity" }, { "rule_key": "series_identity_validity" }, { "rule_key": "source_validity" }, { "rule_key": "chronological_uniqueness" }, { "rule_key": "uom_consistency" } ] }, "snapshot_materialization": { "rule_set_key": "atreides-demand-canonical-v1", "schema_version": "1", "rules": [ { "rule_key": "schema_version" }, { "rule_key": "manifest_row_count" }, { "rule_key": "manifest_series_count" } ] }, "training_preparation": { "rule_set_key": "atreides-demand-canonical-v1", "schema_version": "1", "rules": [ { "rule_key": "chronological_uniqueness" }, { "rule_key": "uom_consistency" }, { "rule_key": "required_projection_dimensions" } ] } }, "series_projection_recipe": { "recipe_key": "sku-store-daily-sum", "target_series_kind": "sku_store", "target_grain": "daily", "aggregation": "sum" }, "feature_recipe": { "steps": [] }, "champion_reuse_policy": { "refresh_active_champion": true, "require_current_config": true }, "evaluator_specs": [ { "registry_key": "mae", "params": {} } ], "primary_metric": { "name": "mae", "direction": "min" }, "promotion_thresholds": { "mae": 1000.0 }, "strategy_specs": [ { "strategy_key": "seasonal-naive", "library_family": "nixtla-statsforecast", "enabled": true, "params_schema": { "model": { "name": "seasonal-naive", "hyperparams": {} }, "intervals": { "levels": [] }, "execution": { "max_workers": 1 } } } ]}Esse é o shape usado pelo seed local do repositório.
Exemplo com paralelismo para nixtla-statsforecast
{ "strategy_key": "auto-arima", "library_family": "nixtla-statsforecast", "enabled": true, "params_schema": { "model": { "name": "auto-arima", "hyperparams": {} }, "intervals": { "levels": [80] }, "execution": { "max_workers": 4 } }}Use max_workers quando quiser permitir que a engine treine séries independentes
em paralelo. A unidade semântica continua sendo uma chamada de train() por
SeriesKey.
Importante:
- paralelismo é otimização operacional;
- avaliação, promoção e ativação continuam independentes por
series_key; - não existe “campeão do painel” nesse contrato.
Exemplo para nixtla-mlforecast
{ "strategy_key": "random-forest", "library_family": "nixtla-mlforecast", "enabled": true, "params_schema": { "model": { "name": "random-forest", "hyperparams": { "n_estimators": 200, "max_depth": 8 } }, "intervals": { "levels": [] }, "execution": { "max_workers": 1 } }}Regras extras dessa família:
- lags, rolling windows e calendário devem ser declarados em
feature_recipe; - hiperparâmetros precisam ser escalares JSON, não listas nem objetos aninhados.
O que congela por versão
No treino, o domínio congela o snapshot base em ModelVersion e congela a
configuração efetivamente resolvida em cada TrainingReference. Isso significa
que:
- trocar
strategy_specsdepois não reescreve versões antigas; - trocar
validation_policy,series_projection_recipe,feature_recipeouchampion_reuse_policycria uma nova configuração efetiva; - cada
ModelInstanceaponta para a versão usada no treino, e seuTrainingReferenceguarda oresolved_config_snapshotmais umconfig_fingerprintcomresolved_config_hash,validation_policy_hash,projection_recipe_hash,feature_recipe_hash,strategy_params_hasheselection_policy_hash; - uma campeã antiga com configuração diferente deixa de ser refrescada por padrão e a série volta para busca completa;
- horizonte, avaliação, feature recipe e parâmetros usados pela estratégia vêm
da configuração resolvida, não dos campos vivos do
ModelDefinition; - a promoção escolhe campeãs com a política congelada na candidata treinada, mesmo que thresholds ou métrica primária mudem antes do retry de promoção;
- artefato, métricas e promoção continuam auditáveis.
Erros de configuração mais comuns
| Cenário | Resultado esperado |
|---|---|
primary_metric fora de evaluator_specs | requisição rejeitada |
execution.max_workers <= 0 | treino falha cedo |
model.name incompatível com o adapter | treino/predição falham com erro explícito |
intervals.levels em modelo point-only | adapter rejeita a estratégia |
Quando usar esta página
Use esta página quando estiver:
- criando um
ModelDefinitionnovo; - ajustando estratégias via HTTP;
- revisando se um payload é compatível com a família escolhida.
Para o comportamento interno dos adapters, veja Nixtla.