Part of #403. Depends on #413.
What
Add quantile and IQR aggregations. These are essential for financial analytics (Value at Risk, distribution shape) and require a params extension to the measure spec.
Model change
Add params to MeasureSpec:
class MeasureSpec(BaseModel):
field: str
aggregation: AggregationFunction = "sum"
alias: str | None = None
sort_by: str | None = None
params: dict[str, Any] | None = None
New aggregation functions
quantile_cont
Continuous (interpolated) quantile. params.q is required.
{ "field": "daily_return", "aggregation": "quantile_cont", "params": { "q": 0.05 }, "alias": "var_95" }
SQL: QUANTILE_CONT("daily_return", 0.05)
When q is an array, returns an object keyed by p{nn}:
{ "field": "price", "aggregation": "quantile_cont", "params": { "q": [0.05, 0.25, 0.5, 0.75, 0.95] }, "alias": "dist" }
Response cell value: { "p05": 142.1, "p25": 155.3, "p50": 161.8, "p75": 168.4, "p95": 179.2 }
Key naming: p + zero-padded two-decimal integer representation of q * 100 (e.g. 0.05 → p05, 0.975 → p97).
SQL: QUANTILE_CONT("price", [0.05, 0.25, 0.5, 0.75, 0.95]) — DuckDB returns an array; Python post-processes into the named object.
quantile_disc
Discrete (actual data value) quantile. Same interface as quantile_cont.
SQL: QUANTILE_DISC("field", q)
iqr
Interquartile range (Q75 − Q25). No params needed.
{ "field": "price", "aggregation": "iqr", "alias": "price_iqr" }
SQL: QUANTILE_CONT("price", 0.75) - QUANTILE_CONT("price", 0.25)
Validation
quantile_cont/quantile_disc without params.q → 422 VALIDATION_ERROR
params.q scalar: must be in [0.0, 1.0]
params.q array: each element must be in [0.0, 1.0], max 20 quantiles per request
iqr with params → ignored (or rejected — decide in implementation)
- All three functions applicable to numeric fields only
Acceptance criteria
Part of #403. Depends on #413.
What
Add quantile and IQR aggregations. These are essential for financial analytics (Value at Risk, distribution shape) and require a
paramsextension to the measure spec.Model change
Add
paramstoMeasureSpec:New aggregation functions
quantile_contContinuous (interpolated) quantile.
params.qis required.{ "field": "daily_return", "aggregation": "quantile_cont", "params": { "q": 0.05 }, "alias": "var_95" }SQL:
QUANTILE_CONT("daily_return", 0.05)When
qis an array, returns an object keyed byp{nn}:{ "field": "price", "aggregation": "quantile_cont", "params": { "q": [0.05, 0.25, 0.5, 0.75, 0.95] }, "alias": "dist" }Response cell value:
{ "p05": 142.1, "p25": 155.3, "p50": 161.8, "p75": 168.4, "p95": 179.2 }Key naming:
p+ zero-padded two-decimal integer representation ofq * 100(e.g.0.05→p05,0.975→p97).SQL:
QUANTILE_CONT("price", [0.05, 0.25, 0.5, 0.75, 0.95])— DuckDB returns an array; Python post-processes into the named object.quantile_discDiscrete (actual data value) quantile. Same interface as
quantile_cont.SQL:
QUANTILE_DISC("field", q)iqrInterquartile range (Q75 − Q25). No
paramsneeded.{ "field": "price", "aggregation": "iqr", "alias": "price_iqr" }SQL:
QUANTILE_CONT("price", 0.75) - QUANTILE_CONT("price", 0.25)Validation
quantile_cont/quantile_discwithoutparams.q→422 VALIDATION_ERRORparams.qscalar: must be in[0.0, 1.0]params.qarray: each element must be in[0.0, 1.0], max 20 quantiles per requestiqrwithparams→ ignored (or rejected — decide in implementation)Acceptance criteria
quantile_contwith scalarqreturns a single numeric valuequantile_contwith arrayqreturns a named object (p05,p50, etc.)quantile_discworks identically with discrete semanticsiqrreturns Q75 − Q25 without paramsparams.qreturns422qout of[0, 1]returns422qwith more than 20 elements returns422