Modelagem de domínio
Por que a modelagem é crítica aqui
Na forecast-engine, modelagem não é apenas desenho de classes: é a camada que impede estados inválidos de chegarem à produção. Cada invariante reduz risco operacional da plataforma.
A camada domain está organizada em três contextos:
demand: histórico observado por série (SKU + loja + granularidade);modeling: definição/versionamento de modelos e instâncias;forecasting: run de inferência, pontos previstos e metadados de fallback.
Para visão aprofundada de regras e objetos, consulte a seção Domínio detalhado.
1. Construir uma chave de série (SeriesKey)
Toda execução de treino/inferência depende de uma identidade canônica de série.
from uuid import UUID
from domain.demand.model import SeriesKeyfrom domain.shared.value_objects import ExternalKey, OrganizationId, TimeGrain
series_key = SeriesKey( organization_id=OrganizationId(UUID("0194fd5b-5ac2-7e56-b8f0-97d276b57b01")), sku_id=UUID("0194fd5b-5ac2-7e56-b8f0-97d276b57b02"), store_id=UUID("0194fd5b-5ac2-7e56-b8f0-97d276b57b03"), sku_key=ExternalKey(system="erp", value="sku-1"), store_key=ExternalKey(system="erp", value="store-1"), grain=TimeGrain.DAILY,)Regras importantes:
organization_id,sku_id,store_idnão podem ser UUID nulo;ExternalKey.systemé normalizado para minúsculo;ExternalKey.valueé normalizado para maiúsculo.
2. Registrar observações de demanda
from datetime import datefrom decimal import Decimal
from domain.demand.model import DemandObservation, DemandSeriesfrom domain.shared.value_objects import DemandQuantity, UomCode
series = DemandSeries(key=series_key)series.record_observation( DemandObservation( period_start=date(2026, 1, 1), quantity=DemandQuantity(value=Decimal("10"), uom=UomCode("EA")), source="erp_sync", ))Invariantes de DemandSeries:
- não aceita período duplicado (
DuplicatePeriodError); - mantém ordenação temporal consistente;
- exige UOM homogênea na série;
- valida compatibilidade entre
period_startegrain.
3. Definir um modelo (ModelDefinition)
from domain.modeling.model import ( EvaluatorSpec, HorizonSpec, LagSpec, MetricDirection, MetricSpec, ModelDefinition, ModelName, StrategySpec, WindowSpec,)from domain.shared.value_objects import TimeGrain
model = ModelDefinition( organization_id=series_key.organization_id, name=ModelName("baseline-xgboost"), target_selector="sku:*|store:*", horizon_spec=HorizonSpec(periods=7, grain=TimeGrain.DAILY, step=1), window_spec=WindowSpec(lookback_periods=56, min_history_periods=28), lag_spec=LagSpec(lags=(1, 7, 14)), evaluator_specs=(EvaluatorSpec(registry_key="mae"),), strategy_specs=( StrategySpec( strategy_key="xgboost", library_family="nixtla-statsforecast", params_schema={ "model": { "name": "auto-arima", "hyperparams": {"season_length": 7}, }, "features": { "date_features": ["dayofweek"], "static_features": ["sku_key", "store_key"], "known_future_features": [], }, "intervals": {"levels": [80, 95]}, }, ), ), primary_metric=MetricSpec(name="mae", direction=MetricDirection.MIN), promotion_thresholds={"mae": 2.0},)Invariantes de ModelDefinition:
- ao menos um avaliador e uma estratégia;
primary_metricdeve existir entre os avaliadores;- ao menos uma estratégia habilitada (
enabled=True); horizon_spec.grainpermitido emallowed_target_grains;- chaves de estratégia normalizadas em slug.
Convenção recomendada para library_family e params_schema
library_family agora deve ser tratado como seletor de família de engine, não
como rótulo solto. As convenções atuais para estratégias Nixtla são:
nixtla-statsforecastnixtla-mlforecast
params_schema aceita árvores JSON-like aninhadas (dict + list + escalares).
A convenção padrão para novas estratégias é:
{ "model": { "name": "auto-arima", "hyperparams": { "season_length": 7 } }, "features": { "date_features": ["dayofweek"], "static_features": ["sku_key", "store_key"], "known_future_features": [] }, "intervals": { "levels": [80, 95] }, "execution": { "training_mode": "series" }}Notas operacionais:
model.nameé obrigatório para adapters que seguem essa convenção;model.hyperparams,features.*eintervals.levelstêm defaults vazios;execution.training_modeé opcional e suportaseries(default) ebatch;known_future_featuresjá pode ser declarado, mas os adapters concretos ainda podem não consumi-lo em runtime.
4. Evoluir versões e instâncias
from domain.modeling.model import ModelInstanceStatus
model_version = model.ensure_current_version()
instance = model.add_instance( series_key=series_key, model_version=model_version.version, strategy_key="xgboost", status=ModelInstanceStatus.DRAFT, artifact_uri="s3://bucket/model-v1.bin", metric_snapshot={"mae": 0.8},)
model.promote_instance(series_key=series_key, version=instance.version)Regra crítica para produção: só pode existir uma instância ativa por série.
Observação de lineage:
ModelVersionrepresenta o snapshot congelado da definição do modelo;ModelInstance.versioncontinua sendo a versão do artefato treinado por série;ModelInstance.model_versionliga o artefato ao snapshot de configuração que abasteceu o treino.
5. Criar um ForecastRun
from domain.forecasting.model import ForecastRunfrom domain.modeling.model import HorizonSpecfrom domain.shared.value_objects import UomCode
run = ForecastRun( organization_id=series_key.organization_id, model_definition_id=model.id, model_version=1, series_key=series_key, horizon_spec=HorizonSpec(periods=7, grain=TimeGrain.DAILY, step=1), expected_uom=UomCode("EA"),)Invariantes de previsão:
horizon_spec.graindeve coincidir comseries_key.grain;- run completo deve ter exatamente
horizon_spec.periodspontos; - sequência temporal deve ser monotônica e alinhada ao
step; - metadados de fallback (
degraded,fallback_from_version,executed_model_version) devem ser consistentes.