Control Plane vs Data-Preparation
Regra simples
- se a pergunta é “qual modelo está ativo, por quê e para quem?”, isso pertence ao
control plane; - se a pergunta é “quais linhas entram no treino, como o snapshot é lido e como os frames são particionados?”, isso pertence ao boundary de
data-preparation.
Essa separação existe para deixar o código honesto com o problema real do produto: metadado de modelagem é relativamente pequeno e durável; histórico de demanda é grande, columnar e transitório.
Control plane
Responsável por:
ModelDefinition,ModelVersioneModelInstance;- submissão de workflows de treino ao Airflow;
- auditoria de promoção;
- promoção transacional de múltiplos campeões;
ModelInstanceguarda lineage de treino;- lineage explícita de treino por
snapshot_idem instâncias e resultados de submissão; - metadados futuros de feature catalog, lineage e configuração de features.
Fonte de verdade:
- ClickHouse para estado operacional e governança;
- MLflow para tracking de treino, packaging e registry sync atrás de adapters de
infra.
Tipos centrais:
SeriesKeyModelDefinitionModelInstance
Portas importantes:
ModelingRepositoryPortDemandSnapshotManifestRepositoryPortForecastingDatasetOperationsPort
Data-preparation
Responsável por:
- descrever snapshots canônicos;
- persistir manifests de snapshot explicitamente;
- validar schema e contagens declaradas do snapshot;
- validar schema e invariantes estruturais sobre frames por uma única porta;
- carregar e persistir frames canônicos de treino por
snapshot_id; - fazer split temporal de holdout;
- preparar a base para future feature frames e joins de catálogo.
Fonte de verdade:
- parquet / filesystem / S3 para snapshots e outros payloads pesados;
- memória apenas como working set de execução.
Tipos centrais:
DemandSnapshotManifestTRAINING_FEATURE_FRAME_SCHEMAPredictResult.forecast_frame
Contrato compartilhado:
- os schemas e nomes de colunas canônicos vivem em
control/data_preparation/frame_schemas.py; - adapters concretos podem validar e converter frames, mas não devem virar fonte de verdade para outros adapters ou estratégias;
- não há adaptação HTTP para previsão online no desenho atual; a inferência
suportada é batch/offline, via DAG
model_prediction, e existe como exceção explícita do serviço command-side.
Implementações concretas hoje:
DemandSnapshotManifestRepositorySparkTabularDataRepositorySparkDemandIngestionPipelinewrite_projected_series_framePolarsTrainingDatasetOperations
Validação de input e frames
O único motor de validação de demanda é DemandValidationCatalog em
control/data_preparation/validation. Ele executa as regras concretas
registradas pelo adapter usando um contexto tipado de validação. O contexto
define a fase e o runtime disponível: ingestão recebe frame e
organization_id; materialização também carrega DemandSnapshotManifest; treino
e predição carregam manifesto e series_projection_recipe.
DemandValidationCatalog.validate(context=..., rule_set=...) rejeita regras
incompatíveis com a fase do contexto. A strategy dessa fase resolve os params
finais da regra: usa params declarados na configuração quando existem e deriva
params do runtime tipado quando a regra depende da fase. Regras de
manifesto (schema_version, manifest_row_count, manifest_series_count)
recebem valores derivados do DemandSnapshotManifest; regras de projeção
(required_projection_dimensions) recebem valores derivados da
series_projection_recipe. A configuração externa pode declarar
validation_policies agrupadas por fase, e o validador rejeita regras que exigem
contexto indisponível naquela fase.
O catálogo de regras é semanticamente único, mas cada adapter registra apenas o
que executa no runtime atual. Spark cobre ingestão, projeção e materialização do
parquet particionado por SeriesKey.series_id; Polars cobre leitura dessas
partições, treino e predição. A execução concreta fica
organizada em infra/data_preparation/{polars,spark}/validation com
contexts.py, rules.py e catalog.py.
No runtime de jobs, a materialização carrega o payload e entrega o frame ao
pipeline de ingestão do adapter. Esse pipeline executa a política da fase de
ingestão pelo catálogo de regras e só depois canonicaliza o frame para
persistência. Não há validador legado de input bruto fora do sistema
ValidationRules.
Onde SeriesKey cruza os dois lados
SeriesKey continua no domínio porque é a identidade estável usada para:
- versionamento de instâncias;
- promoção;
- nomes derivados de artefatos;
- auditoria.
Ao mesmo tempo, essa identidade aparece embutida nos frames canônicos do data
data-preparation. Isso é esperado: o frame carrega a identidade sem virar aggregate de
domínio.
O vínculo entre treino e dados agora é outro: snapshot_id identifica o
snapshot exato usado para gerar uma instância treinada. SeriesKey continua
identificando a série; snapshot_id identifica o dataset.
O que não volta para o domínio
O histórico de demanda não é mais representado como aggregate rico no domínio.
Ele entra, é validado e circula como frame canônico via control/data_preparation
e infra/data_preparation/spark.
Como isso prepara o feature catalog
Com o feature catalog atual:
- o
control planeguarda definição, versão, lineage e vínculo de features comModelDefinition; FeatureRecipecontém chamadas configuradas a definiçõesFeatureStepcanônicas, que validam o payload contra o schema tipado do próprio step;FeatureEngineeringCatalogresolve essas chamadas para executores concretos do adapter;- Polars e Spark registram os mesmos step keys (
target.lags.v1,target.rolling.v1,target.ewm.v1,calendar.date_parts.v1,calendar.fourier.v1,calendar.br_holidays.v1); - o adapter de
data-preparationlê a base canônica, projeta as chaves mínimas do schemafeature_catalog_basee materializa o frame enriquecido de treino; - as estratégias continuam recebendo um
feature_framecanônico na borda final.
Regra prática para novas abstrações
- não criar
portpara helper interno; - criar
portquando houver boundary real de runtime ou integração externa; - manter schemas nomeados em
control/data_preparation/frame_schemas.py; - manter regras de ciclo de vida e governança no
control plane.