feat(api): mode agrégation sur /data (groupby + agrégats) (#78)
This commit is contained in:
+26
-5
@@ -3,8 +3,8 @@ from flask_smorest import Blueprint, abort
|
|||||||
|
|
||||||
from src.api import tracking
|
from src.api import tracking
|
||||||
from src.api.auth import require_token
|
from src.api.auth import require_token
|
||||||
from src.api.filters import FilterError, build_where
|
from src.api.filters import FilterError, build_where, parse_aggregators
|
||||||
from src.db import count_marches, query_marches
|
from src.db import aggregate_marches, count_marches, query_marches
|
||||||
from src.db import schema as duckdb_schema
|
from src.db import schema as duckdb_schema
|
||||||
from src.utils.data import DATA_SCHEMA
|
from src.utils.data import DATA_SCHEMA
|
||||||
|
|
||||||
@@ -151,13 +151,34 @@ def data():
|
|||||||
columns = _parse_columns()
|
columns = _parse_columns()
|
||||||
count_results = request.args.get("count_results", "true").lower() != "false"
|
count_results = request.args.get("count_results", "true").lower() != "false"
|
||||||
|
|
||||||
|
args = list(request.args.items(multi=True))
|
||||||
try:
|
try:
|
||||||
where_sql, params, order_sql = build_where(
|
agg = parse_aggregators(args, duckdb_schema)
|
||||||
list(request.args.items(multi=True)), duckdb_schema
|
where_sql, params, order_sql = build_where(args, duckdb_schema)
|
||||||
)
|
|
||||||
except FilterError as e:
|
except FilterError as e:
|
||||||
abort(400, message=str(e), errors={"field": e.field})
|
abort(400, message=str(e), errors={"field": e.field})
|
||||||
|
|
||||||
|
if agg is not None:
|
||||||
|
if columns:
|
||||||
|
abort(
|
||||||
|
400,
|
||||||
|
message="`columns` ne peut pas être combiné avec une agrégation",
|
||||||
|
)
|
||||||
|
df = aggregate_marches(
|
||||||
|
select_sql=agg.select_sql,
|
||||||
|
where_sql=where_sql,
|
||||||
|
params=params,
|
||||||
|
group_by=agg.group_by_sql,
|
||||||
|
limit=page_size,
|
||||||
|
offset=(page - 1) * page_size,
|
||||||
|
)
|
||||||
|
df_ready = df.with_columns(cs.temporal().cast(pl.String))
|
||||||
|
return {
|
||||||
|
"data": df_ready.to_dicts(),
|
||||||
|
"meta": {"page": page, "page_size": page_size},
|
||||||
|
"links": _build_links(page, page_size, None),
|
||||||
|
}
|
||||||
|
|
||||||
df = query_marches(
|
df = query_marches(
|
||||||
where_sql=where_sql,
|
where_sql=where_sql,
|
||||||
params=params,
|
params=params,
|
||||||
|
|||||||
@@ -112,3 +112,44 @@ def test_data_differs_excludes_value(api_client, valid_token_header):
|
|||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
body = resp.get_json()
|
body = resp.get_json()
|
||||||
assert all(row["uid"] != uid for row in body["data"])
|
assert all(row["uid"] != uid for row in body["data"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_aggregation_groupby_count(api_client, valid_token_header):
|
||||||
|
client, _ = api_client
|
||||||
|
resp = client.get(
|
||||||
|
"/api/v1/data?acheteur_departement_code__groupby&uid__count",
|
||||||
|
headers=valid_token_header,
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
body = resp.get_json()
|
||||||
|
assert body["data"], "agrégation vide ?"
|
||||||
|
for row in body["data"]:
|
||||||
|
assert set(row.keys()) == {"acheteur_departement_code", "uid__count"}
|
||||||
|
assert "total" not in body["meta"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_aggregation_global_count(api_client, valid_token_header):
|
||||||
|
client, _ = api_client
|
||||||
|
resp = client.get("/api/v1/data?uid__count", headers=valid_token_header)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
body = resp.get_json()
|
||||||
|
assert len(body["data"]) == 1
|
||||||
|
assert "uid__count" in body["data"][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_aggregation_with_filter(api_client, valid_token_header):
|
||||||
|
client, _ = api_client
|
||||||
|
resp = client.get(
|
||||||
|
"/api/v1/data?acheteur_departement_code__groupby&uid__count&montant__greater=0",
|
||||||
|
headers=valid_token_header,
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_data_aggregation_with_columns_returns_400(api_client, valid_token_header):
|
||||||
|
client, _ = api_client
|
||||||
|
resp = client.get(
|
||||||
|
"/api/v1/data?uid__count&columns=uid",
|
||||||
|
headers=valid_token_header,
|
||||||
|
)
|
||||||
|
assert resp.status_code == 400
|
||||||
|
|||||||
Reference in New Issue
Block a user