The canonical praxis.stats contract is being expanded
additively for group-by and timeseries.
Current platform intent for this extension:
metric remains valid and backward compatiblemetrics[] becomes an optional public request field for
group-by and timeseriesmetrics[] and
per-bucket/per-point valuesvalue remains the compatibility field for the primary
metricgroup-by and timeseries are now executable
with multi-metric payloads in the starter JPA runtimedistribution remains mono-metric in this phase until
the JPA executor is expandedO praxis-metadata-starter já centraliza a superfície
canônica de leitura para recursos genéricos:
POST /filterPOST /filter/cursorPOST /locatePOST /options/filterEssas operações resolvem listagem, paginação, locate e projeções leves, mas a plataforma ainda não oferece uma forma canônica de consultar KPIs e agregações sobre o mesmo conjunto filtrado.
Na prática, isso empurra apps consumidores para uma das alternativas erradas:
FilterDTO -> SpecificationA solução correta de plataforma é incorporar uma família canônica de
estatísticas filtradas no praxis-metadata-starter,
reaproveitando a mesma semântica de filtro já usada em
/filter.
O objetivo não é criar uma DSL analítica universal. O objetivo é suportar o caso canônico de produto:
com:
Specification<E> já gerada pelo
starterO plano abaixo já está parcialmente implementado no starter.
Status atual da superfície:
POST /stats/group-by
COUNT, SUM, AVG,
MIN, MAXPOST /stats/timeseries
DAY, WEEK,
MONTHCOUNT, SUM, AVG,
MIN, MAXPOST /stats/distribution
mode=TERMS: COUNT, SUM,
AVG, MIN, MAXmode=HISTOGRAM: COUNTGovernança atual:
StatsFieldRegistry e StatsFieldDescriptor
com elegibilidade por papelmetric.field como fonte canônica para métricas
numéricasRestrições deliberadas ainda vigentes:
HISTOGRAM continua restrito a COUNTPOST /stats/group-byPOST /stats/timeseriesPOST /stats/distributionTodos devem operar sobre o mesmo universo filtrado pelo
FD extends GenericFilterDTO.
O frontend não deve mandar uma consulta analítica arbitrária no estilo SQL ou Elasticsearch. O starter deve aceitar apenas requests tipados, pequenos e governados.
FD já existente por recursocount como métrica obrigatóriasum, avg, min,
max como métricas opcionais para campos elegíveisgroup-by unidimensionaltimeseries simples por campo temporal elegíveldistribution em modo terms e
histogram básicoAlém do contrato HTTP dos endpoints, o starter agora também consegue
refletir exemplos operacionais no catálogo derivado de
/schemas/filtered:
x-ui.operationExamples.requestx-ui.operationExamples.responseEsse bloco é derivado dos examples/example
publicados no OpenAPI da operação e serve para catálogos, playgrounds e
frontends metadata-driven que precisem exibir exemplos prontos de uso
sem manter documentação paralela.
Observações de contrato:
schemaType solicitado/schemas/filteredEndpoint:
POST /stats/group-byRequest canônico:
filterfieldmetriclimitorderBySemântica mínima:
Exemplo conceitual:
{
"filter": {
"status": "ATIVO"
},
"field": "departamentoId",
"metric": {
"operation": "count"
},
"limit": 10,
"orderBy": "value_desc"
}Endpoint:
POST /stats/timeseriesRequest canônico:
filterfieldgranularitymetricfromtofillGapsSemântica mínima:
day,
week, month)Endpoint:
POST /stats/distributionRequest canônico:
filterfieldmodemetricbucketSizebucketCountlimitSemântica mínima:
terms para enum/texto canônico elegívelhistogram para número/data em cenários simplesO v1 deve ter DTOs explícitos para evitar payloads ambíguos.
metricfieldbucketsCada bucket:
keylabelvaluecountmetricfieldgranularitypointsCada ponto:
startendlabelvaluecountContrato JSON estabilizado:
start e end são serializados como string
ISO yyyy-MM-ddlabel é a representação textual estável do bucketvalue representa a métrica solicitadacount permanece presente mesmo quando
value vem de métrica numéricaExemplo:
{
"field": "createdOn",
"granularity": "DAY",
"metric": {
"operation": "SUM",
"field": "salary"
},
"points": [
{
"start": "2026-03-01",
"end": "2026-03-01",
"label": "2026-03-01",
"value": 25.0,
"count": 2
}
]
}metricfieldmodebucketsCada bucket:
fromtokeylabelvaluecountStats não devem ser implicitamente suportadas por todo recurso.
O starter já possui ResourceCapabilities. O caminho
correto é estender essa família com capacidades específicas para stats,
por exemplo:
statsGroupBystatsTimeSeriesstatsDistributionNo nível do service, a capability precisa distinguir pelo menos:
O ponto mais importante do desenho é impedir agregação arbitrária.
O v1 precisa de uma camada canônica para declarar:
group-bytimeseriesdistributionIsso pode ser modelado com um registry explícito por recurso, por exemplo:
StatsFieldRegistryStatsFieldDescriptorStatsMetricStatsOperationTypeExemplos de regras:
departamentoId suporta group-by +
countdataAdmissao suporta timeseries +
countsalario suporta distribution(histogram) +
avg + min + maxO starter já tem a peça mais valiosa: a tradução de
FD -> Specification<E>.
O fluxo recomendado do v1 é:
Specification<E> a partir do
filterIsso evita:
/filter e /stats/*Specificationgroup by, truncamento temporal e histogramas
simples/stats/group-by,
/stats/timeseries, /stats/distributionUnsupportedOperationException em
501 quando necessárioApiDocsControllercountO primeiro ganho de produto vem de:
group-by + counttimeseries + countdistribution + countMétricas numéricas adicionais entram apenas se o campo estiver explicitamente governado.
Status:
group-bytimeseriesdistribution em TERMSCOUNT em
distribution/HISTOGRAMNão abrir uma camada de query arbitrária. Para o v1, o executor pode operar com:
CriteriaBuilderCriteriaQuery<Tuple>groupBytimeseries só deve aceitar campos temporais
explicitamente elegíveis e um conjunto pequeno de granularidades.
distribution em dois modosterms: enum/texto controladohistogram: campo numérico elegível com buckets
simplessrc/main/java/org/praxisplatform/uischema/stats/StatsMetric.javaObjetivo:
Tarefas:
COUNTSUMAVGMINMAXCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/TimeSeriesGranularity.javaObjetivo:
Tarefas:
DAYWEEKMONTHCritério de aceite:
timeseries opera com granularidade explícita e
governadasrc/main/java/org/praxisplatform/uischema/stats/DistributionMode.javaObjetivo:
Tarefas:
TERMSHISTOGRAMCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/dto/StatsMetricRequest.javaObjetivo:
Tarefas:
operationfield opcional para métricas numéricasCritério de aceite:
group-by,
timeseries e distributionsrc/main/java/org/praxisplatform/uischema/stats/dto/GroupByStatsRequest.javaObjetivo:
/stats/group-byTarefas:
filterfieldmetriclimitorderByCritério de aceite:
group-by possui request estável e tipadosrc/main/java/org/praxisplatform/uischema/stats/dto/TimeSeriesStatsRequest.javaObjetivo:
/stats/timeseriesTarefas:
filterfieldgranularitymetricfromtofillGapsCritério de aceite:
timeseries possui request estável e tipadosrc/main/java/org/praxisplatform/uischema/stats/dto/DistributionStatsRequest.javaObjetivo:
/stats/distributionTarefas:
filterfieldmodemetricbucketSizebucketCountlimitCritério de aceite:
distribution possui request estável e tipadosrc/main/java/org/praxisplatform/uischema/stats/dto/response/...Objetivo:
Tarefas:
GroupByStatsResponseTimeSeriesStatsResponseDistributionStatsResponseCritério de aceite:
src/main/java/org/praxisplatform/uischema/annotation/ResourceCapabilities.javaObjetivo:
Tarefas:
statsGroupBystatsTimeSeriesstatsDistributionCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/StatsSupportMode.javaObjetivo:
Tarefas:
AUTODISABLEDCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/StatsFieldDescriptor.javaObjetivo:
Tarefas:
Critério de aceite:
src/main/java/org/praxisplatform/uischema/stats/StatsFieldRegistry.javaObjetivo:
Tarefas:
Critério de aceite:
group-by, timeseries e
distribution não dependem de campos arbitráriossrc/main/java/org/praxisplatform/uischema/stats/StatsEligibility.javaObjetivo:
Tarefas:
histogram e timeseriesCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/service/StatsQueryExecutor.javaObjetivo:
Tarefas:
groupBytimeSeriesdistributionCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/service/jpa/JpaStatsQueryExecutor.javaObjetivo:
Tarefas:
Specification<E>termshistogramCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/service/jpa/StatsCriteriaSupport.javaObjetivo:
Tarefas:
Critério de aceite:
src/main/java/org/praxisplatform/uischema/stats/service/jpa/TimeSeriesBucketExpressionFactory.javaObjetivo:
Tarefas:
Critério de aceite:
timeseries não vaza para o restante
da infraestruturasrc/main/java/org/praxisplatform/uischema/service/base/BaseCrudService.javaObjetivo:
Tarefas:
groupByStats(...)timeSeriesStats(...)distributionStats(...)Critério de aceite:
src/main/java/org/praxisplatform/uischema/service/base/AbstractBaseCrudService.javaObjetivo:
Tarefas:
GenericSpecificationsBuilderStatsQueryExecutorCritério de aceite:
src/main/java/org/praxisplatform/uischema/service/base/AbstractReadOnlyService.javaObjetivo:
Tarefas:
Critério de aceite:
src/main/java/org/praxisplatform/uischema/controller/base/AbstractCrudController.javaObjetivo:
Tarefas:
POST /stats/group-byPOST /stats/timeseriesPOST /stats/distributionUnsupportedOperationException em
501Critério de aceite:
src/main/java/org/praxisplatform/uischema/controller/base/AbstractReadOnlyController.javaObjetivo:
Tarefas:
Critério de aceite:
src/main/java/org/praxisplatform/uischema/configuration/OpenApiUiSchemaAutoConfiguration.javaObjetivo:
Tarefas:
StatsQueryExecutor@ConditionalOnMissingBean quando fizer
sentidoCritério de aceite:
src/main/java/org/praxisplatform/uischema/stats/StatsProperties.javaObjetivo:
Tarefas:
enabledmaxBucketsmaxSeriesPointsdefaultModeCritério de aceite:
src/main/java/org/praxisplatform/uischema/controller/docs/ApiDocsController.javaObjetivo:
Tarefas:
/stats/group-by/stats/timeseries/stats/distributionCritério de aceite:
src/main/java/org/praxisplatform/uischema/controller/base/doc-files/endpoints-overview.htmlObjetivo:
Tarefas:
group-by,
timeseries e distributionCritério de aceite:
docs/technical/README.mdObjetivo:
Critério de aceite:
src/test/java/org/praxisplatform/uischema/stats/StatsEligibilityTest.javaObjetivo:
src/test/java/org/praxisplatform/uischema/stats/service/jpa/JpaStatsQueryExecutorTest.javaObjetivo:
Cenários mínimos:
group-by + counttimeseries + countdistribution terms + countdistribution histogram + countSpecificationsrc/test/java/org/praxisplatform/uischema/controller/base/...Objetivo:
501 para recurso sem suporte200 para recurso elegível realpraxis-api-quickstart/src/test/java/...Objetivo:
Recursos candidatos iniciais:
group-bytimeseriesdistributionCritério de aceite:
group-by + counttimeseries + countdistribution terms + countdistribution histogram para campos numéricos
elegíveisO v1 só deve ser considerado pronto quando:
group-by, timeseries e
distribution existirem como endpoints canônicosFD do
recurso/filter e /stats/*timeseriesComeçar pelo menor corte de plataforma com valor real:
GroupByStatsRequestGroupByStatsResponseStatsMetricStatsFieldRegistryStatsEligibilityJpaStatsQueryExecutor com
group-by + countDepois disso:
AbstractBaseCrudServicePOST /stats/group-bySó então ampliar para timeseries e
distribution.