From 858d835464929fe3276fee640a631990d84c79d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Flambard?= Date: Wed, 11 Feb 2026 16:58:35 +0100 Subject: [PATCH] [FIX] Update TimeSeries Endpoint to choose all regions or multiple ones --- api/app/Dto/TaxTimeSeries.php | 14 +++++------ api/app/Services/TaxeStatService.php | 32 ++++++++++++++++++++----- api/app/State/TaxTimeSeriesProvider.php | 14 +++++------ 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/api/app/Dto/TaxTimeSeries.php b/api/app/Dto/TaxTimeSeries.php index f460311..bbf4466 100644 --- a/api/app/Dto/TaxTimeSeries.php +++ b/api/app/Dto/TaxTimeSeries.php @@ -11,32 +11,32 @@ use App\State\TaxTimeSeriesProvider; #[ApiResource( shortName: 'TaxTimeSeries', - description: 'Average tax rate evolution by year for a given region', + description: 'Average tax rate evolution by year, grouped by region', paginationEnabled: false, operations: [ new Get( uriTemplate: '/taxes/timeseries', name: 'tax_timeseries', openapi: new Operation( - summary: 'Get average tax rate evolution by year for a given region', - description: 'Returns average tax rate per year for a given region and tax type, with optional year range.', + summary: 'Get average tax rate evolution by year for one, several, or all regions', + description: 'Returns average tax rate per year and per region for a given tax type, with optional year range. If no region is specified, all regions are returned. Multiple regions can be comma-separated (e.g. region=Normandie,Bretagne).', responses: [ - '200' => new Response(description: 'Array of years with their average tax rate'), - '400' => new Response(description: 'Invalid or missing parameters (region and tax_type are required)'), + '200' => new Response(description: 'Object with regions array and data grouped by region'), + '400' => new Response(description: 'Invalid parameters (tax_type is required)'), ], ), provider: TaxTimeSeriesProvider::class, ), ], )] -#[QueryParameter(key: 'region', description: 'French region name (e.g. Normandie, Île-de-France)', schema: ['type' => 'string'], required: true)] +#[QueryParameter(key: 'region', description: 'French region name(s), comma-separated (e.g. Normandie,Île-de-France). Omit for all regions.', schema: ['type' => 'string'])] #[QueryParameter(key: 'tax_type', description: 'Tax type to analyze', schema: ['type' => 'string', 'enum' => ['tfpnb', 'tfpb', 'th', 'cfe']], required: true)] #[QueryParameter(key: 'start_year', description: 'Start year for the range (default: earliest available)', schema: ['type' => 'integer'])] #[QueryParameter(key: 'end_year', description: 'End year for the range (default: latest available)', schema: ['type' => 'integer'])] class TaxTimeSeries { public function __construct( - public readonly string $region = '', + public readonly array $regions = [], public readonly string $tax_type = '', public readonly ?int $start_year = null, public readonly ?int $end_year = null, diff --git a/api/app/Services/TaxeStatService.php b/api/app/Services/TaxeStatService.php index 765edb3..e4b436a 100644 --- a/api/app/Services/TaxeStatService.php +++ b/api/app/Services/TaxeStatService.php @@ -65,19 +65,29 @@ class TaxeStatService } /** - * Get time series data: average tax rate by year for a given region. + * Get time series data: average tax rate by year, grouped by region. + * + * @param array $regions Region names to filter (empty = all regions) */ - public function getTimeSeries(string $region, string $taxType, ?int $startYear = null, ?int $endYear = null): array + public function getTimeSeries(array $regions, string $taxType, ?int $startYear = null, ?int $endYear = null): array { $this->validateAndGetColumn($taxType); $rateColumn = $taxType . '_percentage'; $query = Taxe::query() ->join('departments', 'taxes.department_id', '=', 'departments.id') - ->where('departments.region_name', $region) - ->groupBy('taxes.year') + ->groupBy('departments.region_name', 'taxes.year') + ->orderBy('departments.region_name') ->orderBy('taxes.year') - ->select('taxes.year', DB::raw("ROUND(AVG(taxes.{$rateColumn}), 2) as avg_rate")); + ->select( + 'departments.region_name as region', + 'taxes.year', + DB::raw("ROUND(AVG(taxes.{$rateColumn}), 2) as avg_rate"), + ); + + if (!empty($regions)) { + $query->whereIn('departments.region_name', $regions); + } if ($startYear !== null) { $query->where('taxes.year', '>=', $startYear); @@ -86,7 +96,17 @@ class TaxeStatService $query->where('taxes.year', '<=', $endYear); } - return $query->get()->toArray(); + $rows = $query->get(); + + $grouped = []; + foreach ($rows as $row) { + $grouped[$row->region][] = [ + 'year' => $row->year, + 'avg_rate' => $row->avg_rate, + ]; + } + + return $grouped; } /** diff --git a/api/app/State/TaxTimeSeriesProvider.php b/api/app/State/TaxTimeSeriesProvider.php index 73d6131..f71d4c7 100644 --- a/api/app/State/TaxTimeSeriesProvider.php +++ b/api/app/State/TaxTimeSeriesProvider.php @@ -19,28 +19,28 @@ class TaxTimeSeriesProvider implements ProviderInterface { $request = $context['request'] ?? null; - $region = $request?->query->get('region'); + $regionParam = $request?->query->get('region'); $taxType = $request?->query->get('tax_type'); $startYear = $request?->query->get('start_year'); $endYear = $request?->query->get('end_year'); - if (!$region) { - throw new BadRequestHttpException('The "region" parameter is required.'); - } - if (!$taxType || !in_array($taxType, Taxe::ALLOWED_STAT_FIELDS, true)) { throw new BadRequestHttpException( sprintf('Invalid tax_type "%s". Allowed: %s', $taxType, implode(', ', Taxe::ALLOWED_STAT_FIELDS)) ); } + $regions = $regionParam + ? array_map('trim', explode(',', $regionParam)) + : []; + $startYearInt = $startYear !== null ? (int) $startYear : null; $endYearInt = $endYear !== null ? (int) $endYear : null; - $data = $this->taxeStatService->getTimeSeries($region, $taxType, $startYearInt, $endYearInt); + $data = $this->taxeStatService->getTimeSeries($regions, $taxType, $startYearInt, $endYearInt); return new TaxTimeSeries( - region: $region, + regions: $regions, tax_type: $taxType, start_year: $startYearInt, end_year: $endYearInt, -- GitLab