Skip to content

Adapters customizados

Este guia cobre a integração de métodos próprios de forecast que não cabem nas famílias já suportadas pela engine.

Se a mudança for apenas adicionar mais um modelo dentro de uma família já existente, prefira estender o catálogo atual da família em vez de criar um adapter novo.

Decisão rápida

CenárioCaminho recomendado
mesmo library_family e mesma convenção de artefatoampliar catálogo e testes da família existente
nova lógica de treino/prediçãocriar uma nova família em src/infra/modeling/<family>/

Contrato mínimo

O ponto de extensão principal está em src/control/modeling/shared/strategy_ports.py, com os DTOs de chamada em src/control/modeling/shared/contracts.py.

Todo adapter customizado precisa implementar ModelStrategy:

class ModelStrategy(Protocol):
def train(self, request: TrainRequest) -> TrainResult: ...
def predict(self, request: PredictRequest) -> PredictResult: ...

Regras que a engine espera:

  • train() precisa retornar métricas e também artifact_uri ou artifact_payload;
  • predict() precisa ler o artefato indicado em request.artifact_uri;
  • PredictResult.forecast_frame precisa voltar como frame canônico de forecast (period_start, value, uom, lower, upper, confidence);
  • erros de treino ou predição devem falhar explicitamente, sem falso sucesso.

Contrato de dados

O feature_frame entregue ao adapter vem do boundary canônico de data-preparation. O control trata esse payload de forma genérica; a representação concreta fica no adapter. No runtime atual de treino e predição, essa execução é Polars, e a conversão para formatos externos deve continuar restrita a infra/modeling/<family>/.

No treino e na predição, o frame de demanda chega com estas colunas:

  • organization_id
  • sku_id
  • store_id
  • sku_system
  • sku_key
  • store_system
  • store_key
  • grain
  • period_start
  • quantity
  • uom
  • source
  • created_at

Na volta, o adapter precisa produzir um frame de forecast com:

  • period_start
  • value
  • uom
  • lower
  • upper
  • confidence

Esses nomes não devem ser trocados no adapter. O mapeamento para formatos externos deve acontecer dentro de src/infra/modeling/..., não no domínio, no control plane ou no data plane.

Passo a passo

1. Criar a família do adapter

Crie um diretório dedicado, por exemplo:

src/infra/modeling/my-family/

Mantenha ali:

  • a implementação concreta da estratégia;
  • helpers de transformação de frame;
  • codec de artefato, se houver;
  • bootstrap de registro da família.

Evite espalhar detalhes da biblioteca em control, control/data_preparation ou domain.

2. Implementar treino e predição

A implementação costuma seguir este fluxo:

  1. validar que request.library_family bate com a família esperada;
  2. converter o frame canônico para o formato da biblioteca;
  3. treinar o modelo com request.params e as colunas já preparadas no frame;
  4. serializar o artefato;
  5. devolver TrainResult(metrics=..., artifact_payload=...);
  6. na predição, recarregar o artefato e converter a saída de volta ao frame canônico.

request.params já chega como o snapshot efetivo do params_schema resolvido para a estratégia. O adapter não precisa consultar repositório nem ler configuração externa para descobrir hiperparâmetros.

3. Registrar a estratégia nos jobs

Os jobs chamados pelo Airflow resolvem estratégias por strategy_key usando DefaultStrategyCatalog.

Na prática:

  1. crie um helper como register_my_family_strategies(...);
  2. registre cada strategy_key com a implementação concreta;
  3. chame esse helper em src/infra/jobs/bootstrap.py.

Sem esse registro, o treino falha com Unknown strategy key.

4. Expor a família na configuração HTTP

Se o ModelDefinition for criado ou atualizado via API, a família nova precisa registrar sua chave de família no StrategyCatalogPort.

No runtime atual, isso acontece no helper de bootstrap da família, por exemplo register_nixtla_strategies(...), chamando register_library_family(...).

É esse catálogo que a aplicação consulta para validar:

  • se o library_family é conhecido;
  • se o strategy_key tem uma implementação registrada.

src/infra/http/schemas.py só traduz payloads para tipos internos; quem decide se a família é válida agora é a aplicação.

5. Adicionar testes

O mínimo esperado é:

  • teste do adapter para train() e predict();
  • teste de round-trip do artefato;
  • teste de caso de uso cobrindo registro da estratégia e execução;

Os arquivos mais úteis como referência hoje são:

  • tests/test_nixtla_strategies.py
  • tests/test_engine_use_cases.py
  • tests/test_mlops_adapters.py

6. Documentar a família

Ao finalizar a integração:

  • atualize esta seção de ml-models com a nova família;
  • ajuste Configuração de model definition se o contrato de params_schema mudar;
  • documente limitações reais da integração, sem esconder restrições.

Checklist final

Antes de considerar a integração pronta, confirme:

  • a implementação fica restrita à camada infra/modeling;
  • strategy_key e library_family estão estáveis;
  • train() sempre gera artefato utilizável em predict();
  • o forecast retornado usa o schema canônico;
  • treino e promoção falham claramente quando a família é incompatível;
  • testes cobrem o caminho feliz e os erros de contrato.

Onde continuar