From 4ab52aa93d68d839d409eaa0254be89bfb0701a1 Mon Sep 17 00:00:00 2001
From: Jonathan Fallon
Date: Thu, 8 Feb 2024 15:24:31 +0100
Subject: [PATCH] fix(campaign): campagnes (#2405)
* update Pays Basque Adour (946) description
* update Cannes (1018)
* add PETR Lunevillois S1 2023 campaign
---
.../policy/src/engine/helpers/isInside.ts | 28 +--
.../policy/src/engine/policies/Cannes.html.ts | 61 ++++---
.../src/engine/policies/PaysBasque.html.ts | 61 +++----
.../policies/PetrLunevilloisS12023.html.ts | 22 +++
.../policies/PetrLunevilloisS12023.spec.ts | 162 ++++++++++++++++++
.../engine/policies/PetrLunevilloisS12023.ts | 82 +++++++++
.../policy/src/engine/policies/index.ts | 58 ++++---
7 files changed, 375 insertions(+), 99 deletions(-)
create mode 100644 api/services/policy/src/engine/policies/PetrLunevilloisS12023.html.ts
create mode 100644 api/services/policy/src/engine/policies/PetrLunevilloisS12023.spec.ts
create mode 100644 api/services/policy/src/engine/policies/PetrLunevilloisS12023.ts
diff --git a/api/services/policy/src/engine/helpers/isInside.ts b/api/services/policy/src/engine/helpers/isInside.ts
index 8d2ba34a35..4899b84046 100644
--- a/api/services/policy/src/engine/helpers/isInside.ts
+++ b/api/services/policy/src/engine/helpers/isInside.ts
@@ -4,27 +4,27 @@ import { Feature, MultiPolygon, Polygon, Properties, multiPolygon, point, polygo
import booleanPointInPolygon from '@turf/boolean-point-in-polygon';
interface IsCloseToParams {
- shape: GeoJSON,
+ shape: GeoJSON;
}
export const isInside: StatelessRuleHelper = (
ctx: StatelessContextInterface,
params: IsCloseToParams,
): boolean => {
- const start = point([ctx.carpool.start_lon, ctx.carpool.start_lat]);
- const end = point([ctx.carpool.end_lon, ctx.carpool.end_lat]);
- const shape = getShapeFromGeoJSON(params.shape);
-
- return booleanPointInPolygon(start, shape) || booleanPointInPolygon(end, shape);
+ const start = point([ctx.carpool.start_lon, ctx.carpool.start_lat]);
+ const end = point([ctx.carpool.end_lon, ctx.carpool.end_lat]);
+ const shape = getShapeFromGeoJSON(params.shape);
+
+ return booleanPointInPolygon(start, shape) || booleanPointInPolygon(end, shape);
};
-function getShapeFromGeoJSON(data: GeoJSON): Feature {
- if (data.type === 'Polygon') {
- return polygon(data.coordinates);
- }
+function getShapeFromGeoJSON(data: GeoJSON): Feature {
+ if (data.type === 'Polygon') {
+ return polygon(data.coordinates);
+ }
- if (data.type === 'MultiPolygon') {
- return multiPolygon(data.coordinates);
- }
- throw new Error('Invalid GeoJSON');
+ if (data.type === 'MultiPolygon') {
+ return multiPolygon(data.coordinates);
+ }
+ throw new Error('Invalid GeoJSON');
}
diff --git a/api/services/policy/src/engine/policies/Cannes.html.ts b/api/services/policy/src/engine/policies/Cannes.html.ts
index 1ed75e5128..967174400a 100644
--- a/api/services/policy/src/engine/policies/Cannes.html.ts
+++ b/api/services/policy/src/engine/policies/Cannes.html.ts
@@ -1,28 +1,33 @@
-export const description = `
-
Campagne d’incitation au covoiturage du 01 janvier 2024 au 30 avril 2024
-
-Cette campagne est limitée à
- 50 896,48 euros .
-
-
-Le périmètre géographique de la campagne comprend les zones suivantes
-
-
- - Communauté d'agglomération Cannes Pays de Lérins
-
-Les conducteurs effectuant un trajet entre 2 km et 80 km,
-avec pour origine OU destination le périmètre ci-dessus,
-sont incités selon les règles suivantes :
-
- - De 2 à 15 km : 1.5 euros par trajet par passager transporté.
- - De 15 à 30 km : 0.1 euros par km par passager transporté.
-
-Les restrictions suivantes seront appliquées :
-
- - 150 € maximum pour le conducteur par mois.
- - 6 trajets maximum pour le conducteur par jour.
-
-La campagne est limitée aux opérateurs Klaxit
-proposant des preuves de classe B ou C.
-
-
`;
+export const description = `
+
+
Campagne d’incitation au covoiturage du 01 janvier 2024 au 30 avril 2024
+
+
Cette campagne est limitée à 50 896,48 euros .
+
+
Le périmètre géographique de la campagne comprend les zones suivantes :
+
+
+ - Communauté d'agglomération Cannes Pays de Lérins
+
+
+
+ Les conducteurs effectuant un trajet entre 2 km et 80 km,
+ avec pour origine OU destination le périmètre ci-dessus,
+ sont incités selon les règles suivantes :
+
+
+
+ - De 2 à 15 km : 1.5 euros par trajet par passager transporté.
+ - De 15 à 30 km : 0.1 euros par km par passager transporté.
+
+
+
Les restrictions suivantes seront appliquées :
+
+
+ - 150 € maximum pour le conducteur par mois.
+ - 6 trajets maximum pour le conducteur par jour.
+
+
+
La campagne est limitée à l'opérateur Klaxit proposant des preuves de classes B ou C.
+
+
`;
diff --git a/api/services/policy/src/engine/policies/PaysBasque.html.ts b/api/services/policy/src/engine/policies/PaysBasque.html.ts
index 14eafbedef..1974643cd4 100644
--- a/api/services/policy/src/engine/policies/PaysBasque.html.ts
+++ b/api/services/policy/src/engine/policies/PaysBasque.html.ts
@@ -1,29 +1,32 @@
-export const description = `
-
Campagne d’incitation au covoiturage du 1 avril 2023 au 31 décembre 2023, toute la semaine
-
-Cette campagne est limitée à
- 185 000 euros .
-
-Les trajets de + de 80km sont exclus à partir du 1er janvier 2024
-Les conducteurs et passagers effectuant un trajet d'au moins 5 km sont incités selon les règles suivantes :
-
-
- - De 5 à 20 km : 2 euros par trajet par passager
- - De 20 à 30 km : 0.1 euro par trajet par km par passager
-
-Les passagers effectuant un trajet d'au moins 5 km sont incités selon les règles suivantes :
-
-
- - Les trajets sont gratuits s’il a une origine ou une destination sur le territoire du
- Pays Basque Adour
(la contrepartie n'est pas prise en compte par le RPC)
-
-Les restrictions suivantes seront appliquées :
-
- - 6 trajets maximum pour le conducteur par jour.
- - 150 euros maximum pour le conducteur par mois.
-
-La campagne est limitée à aux opérateurs Klaxit, Karos, Blablacar Daily,
-Mobicoop proposant des preuves de classe C.
-*Mobicoop ne fait plus parti des opérateurs éligibles depuis le 1 Janvier 2024
-
-`;
+/* eslint-disable */
+
+export const description = `
+
+
Campagne d'incitation au covoiturage du 1 avril 2023 au 31 décembre 2024, toute la semaine
+
Cette campagne est limitée à 185 000 euros.
+
Les trajets de + de 80km sont exclus à partir du 1er janvier 2024
+
Les conducteurs et passagers effectuant un trajet d'au moins 5 km sont incités selon les règles suivantes :
+
+
+ - De 5 à 20 km : 2 euros par trajet par passager
+ - De 20 à 30 km : 0.1 euro par trajet par km par passager
+
+
+
Les passagers effectuant un trajet d'au moins 5 km sont incités selon les règles suivantes :
+
+
+ - Les trajets sont gratuits s'il a une origine ou une destination sur le territoire du
+ Pays Basque Adour
(la contrepartie n'est pas prise en compte par le RPC)
+
+
+
Les restrictions suivantes seront appliquées :
+
+
+ - 6 trajets maximum pour le conducteur par jour.
+ - 150 euros maximum pour le conducteur par mois.
+
+
+
La campagne est limitée à aux opérateurs Klaxit, Karos et Blablacar Daily proposant des preuves de classe C.
+
Mobicoop ne fait plus partie des opérateurs éligibles depuis le 1 Janvier 2024
+
+
`;
diff --git a/api/services/policy/src/engine/policies/PetrLunevilloisS12023.html.ts b/api/services/policy/src/engine/policies/PetrLunevilloisS12023.html.ts
new file mode 100644
index 0000000000..4ca3cc947b
--- /dev/null
+++ b/api/services/policy/src/engine/policies/PetrLunevilloisS12023.html.ts
@@ -0,0 +1,22 @@
+/* eslint-disable */
+
+export const description = `
+
+
Campagne d'incitation au covoiturage du 8 janvier 2024 au 7 juin 2024, toute la semaine
+
Cette campagne est limitée à 10 000 euros.
+
+
Les conducteurs effectuant un trajet en covoiturage d'au moins 2 km sont incités selon les règles suivantes :
+
+
+ - De 2 à 60 km : 7 centimes d'euro par km et par trajet par passager transporté
+
+
+
Les restrictions suivantes seront appliquées :
+
+
+ - 2 trajets maximum pour le conducteur par jour.
+
+
+
La campagne est limitée à l'opérateur Mobicoop proposant des preuves de classe C.
+
+
`;
diff --git a/api/services/policy/src/engine/policies/PetrLunevilloisS12023.spec.ts b/api/services/policy/src/engine/policies/PetrLunevilloisS12023.spec.ts
new file mode 100644
index 0000000000..ab6ae705d3
--- /dev/null
+++ b/api/services/policy/src/engine/policies/PetrLunevilloisS12023.spec.ts
@@ -0,0 +1,162 @@
+import test from 'ava';
+import { v4 } from 'uuid';
+import { OperatorsEnum } from '../../interfaces';
+import { makeProcessHelper } from '../tests/macro';
+import { PetrLunevilloisS12023 as Handler } from './PetrLunevilloisS12023';
+
+const defaultPosition = {
+ arr: '54233',
+ com: '54233',
+ aom: '200051134',
+ epci: '200069433',
+ dep: '54',
+ reg: '44',
+ country: 'XXXXX',
+ reseau: '269',
+};
+const defaultLat = 48.5905360901711;
+const defaultLon = 6.499392987670189;
+
+const defaultCarpool = {
+ _id: 1,
+ trip_id: v4(),
+ passenger_identity_uuid: v4(),
+ driver_identity_uuid: v4(),
+ operator_siret: OperatorsEnum.Mobicoop,
+ operator_class: 'C',
+ passenger_is_over_18: true,
+ passenger_has_travel_pass: true,
+ driver_has_travel_pass: true,
+ datetime: new Date('2023-02-01'),
+ seats: 1,
+ duration: 600,
+ distance: 5_000,
+ cost: 20,
+ driver_payment: 20,
+ passenger_payment: 20,
+ start: { ...defaultPosition },
+ end: { ...defaultPosition },
+ start_lat: defaultLat,
+ start_lon: defaultLon,
+ end_lat: 48.58685290576798,
+ end_lon: 6.483696700766759,
+};
+
+const process = makeProcessHelper(defaultCarpool);
+
+test(
+ 'should work with exclusions',
+ process,
+ {
+ policy: { handler: Handler.id },
+ carpool: [
+ { operator_siret: 'not in list' },
+ { operator_siret: OperatorsEnum.Klaxit },
+ { distance: 100 },
+ { distance: 60_000 },
+ { operator_class: 'A' },
+ ],
+ meta: [],
+ },
+ { incentive: [0, 0, 0, 0, 0], meta: [] },
+);
+
+test(
+ 'should work basic with start/end inside aom',
+ process,
+ {
+ policy: { handler: Handler.id },
+ carpool: [
+ { distance: 5_000, driver_identity_uuid: 'one' },
+ { distance: 5_000, seats: 2, driver_identity_uuid: 'one' },
+ { distance: 25_000, driver_identity_uuid: 'two' },
+ { distance: 25_000, seats: 2, driver_identity_uuid: 'two' },
+ ],
+ meta: [],
+ },
+ {
+ incentive: [21, 42, 161, 322],
+ meta: [
+ {
+ key: 'max_amount_restriction.global.campaign.global',
+ value: 546,
+ },
+ ],
+ },
+);
+
+test(
+ 'should work basic with start or end outside aom',
+ process,
+ {
+ policy: { handler: Handler.id },
+ carpool: [
+ // start
+ { distance: 5_000, driver_identity_uuid: 'one', start: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 5_000, seats: 2, driver_identity_uuid: 'one', start: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 25_000, driver_identity_uuid: 'two', start: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 25_000, seats: 2, driver_identity_uuid: 'two', start: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 55_000, driver_identity_uuid: 'two', start: { ...defaultPosition, aom: 'not_in_aom' } },
+
+ // end
+ { distance: 5_000, driver_identity_uuid: 'one', end: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 5_000, seats: 2, driver_identity_uuid: 'one', end: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 25_000, driver_identity_uuid: 'two', end: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 25_000, seats: 2, driver_identity_uuid: 'two', end: { ...defaultPosition, aom: 'not_in_aom' } },
+ { distance: 55_000, driver_identity_uuid: 'two', end: { ...defaultPosition, aom: 'not_in_aom' } },
+ ],
+ meta: [],
+ },
+ {
+ incentive: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ meta: [],
+ },
+);
+
+test(
+ 'should work with global limits',
+ process,
+ {
+ policy: { handler: Handler.id, max_amount: 10_000_00 },
+ carpool: [{ distance: 59_000, driver_identity_uuid: 'one' }],
+ meta: [
+ {
+ key: 'max_amount_restriction.global.campaign.global',
+ value: 9_999_99,
+ },
+ ],
+ },
+ {
+ incentive: [1], // <-- should be 413. capped to 1
+ meta: [
+ {
+ key: 'max_amount_restriction.global.campaign.global',
+ value: 10_000_00,
+ },
+ ],
+ },
+);
+
+test(
+ 'should work with 2 trips per day limit',
+ process,
+ {
+ policy: { handler: Handler.id },
+ carpool: [
+ { distance: 5_000, driver_identity_uuid: 'one' },
+ { distance: 5_000, driver_identity_uuid: 'one' },
+ { distance: 5_000, driver_identity_uuid: 'one' },
+ { distance: 5_000, driver_identity_uuid: 'one' },
+ ],
+ meta: [],
+ },
+ {
+ incentive: [21, 21, 0, 0],
+ meta: [
+ {
+ key: 'max_amount_restriction.global.campaign.global',
+ value: 42,
+ },
+ ],
+ },
+);
diff --git a/api/services/policy/src/engine/policies/PetrLunevilloisS12023.ts b/api/services/policy/src/engine/policies/PetrLunevilloisS12023.ts
new file mode 100644
index 0000000000..23843536a2
--- /dev/null
+++ b/api/services/policy/src/engine/policies/PetrLunevilloisS12023.ts
@@ -0,0 +1,82 @@
+import {
+ OperatorsEnum,
+ PolicyHandlerInterface,
+ PolicyHandlerParamsInterface,
+ PolicyHandlerStaticInterface,
+ StatelessContextInterface,
+} from '../../interfaces';
+import { RunnableSlices } from '../../interfaces/engine/PolicyInterface';
+import {
+ LimitTargetEnum,
+ isOperatorClassOrThrow,
+ isOperatorOrThrow,
+ onDistanceRange,
+ onDistanceRangeOrThrow,
+ perKm,
+ perSeat,
+ watchForGlobalMaxAmount,
+ watchForPersonMaxTripByDay,
+} from '../helpers';
+import { startAndEndAtOrThrow } from '../helpers/startAndEndAtOrThrow';
+import { AbstractPolicyHandler } from './AbstractPolicyHandler';
+import { description } from './PetrLunevilloisS12023.html';
+
+/* eslint-disable-next-line */
+export const PetrLunevilloisS12023: PolicyHandlerStaticInterface = class extends AbstractPolicyHandler implements PolicyHandlerInterface {
+ static readonly id = 'petr_lunevillois_s1_2023';
+ protected operators = [OperatorsEnum.Mobicoop];
+
+ // 7 cts per km per passenger
+ protected slices: RunnableSlices = [
+ {
+ start: 2_000,
+ end: 60_000,
+ fn: (ctx: StatelessContextInterface) => perSeat(ctx, perKm(ctx, { amount: 7, offset: 2_000, limit: 60_000 })),
+ },
+ ];
+
+ constructor(public max_amount: number) {
+ super();
+ this.limits = [
+ ['99911EAF-89AB-C346-DDD5-BD2C7704F935', max_amount, watchForGlobalMaxAmount],
+ ['70CE7566-6FD5-F850-C039-D76AF6F8CEB5', 2, watchForPersonMaxTripByDay, LimitTargetEnum.Driver],
+ ];
+ }
+
+ protected processExclusion(ctx: StatelessContextInterface) {
+ isOperatorOrThrow(ctx, this.operators);
+ onDistanceRangeOrThrow(ctx, { min: 2_000, max: 60_000 });
+ isOperatorClassOrThrow(ctx, ['C']);
+ startAndEndAtOrThrow(ctx, { aom: ['200051134'] });
+ }
+
+ processStateless(ctx: StatelessContextInterface): void {
+ this.processExclusion(ctx);
+ super.processStateless(ctx);
+
+ // Calcul des incitations par tranche
+ let amount = 0;
+ for (const { start, fn } of this.slices) {
+ if (onDistanceRange(ctx, { min: start })) {
+ amount += fn(ctx);
+ }
+ }
+
+ ctx.incentive.set(amount);
+ }
+
+ params(): PolicyHandlerParamsInterface {
+ return {
+ tz: 'Europe/Paris',
+ slices: this.slices,
+ operators: this.operators,
+ limits: {
+ glob: this.max_amount,
+ },
+ };
+ }
+
+ describe(): string {
+ return description;
+ }
+};
diff --git a/api/services/policy/src/engine/policies/index.ts b/api/services/policy/src/engine/policies/index.ts
index e20f17f152..c9451cf14a 100644
--- a/api/services/policy/src/engine/policies/index.ts
+++ b/api/services/policy/src/engine/policies/index.ts
@@ -1,64 +1,66 @@
-import { Cotentin } from './Cotentin';
-import { Pdll2023 } from './Pdll2023';
-import { Vitre } from './Vitre';
-import { Montpellier } from './Montpellier';
import { PolicyHandlerStaticInterface } from '../../interfaces';
+import { Atmb } from './Atmb';
+import { Cannes } from './Cannes';
+import { Cotentin } from './Cotentin';
+import { GrandPoitiers } from './GrandPoitiers';
import { Idfm } from './Idfm';
+import { LaRochelle } from './LaRochelle';
import { Lannion } from './Lannion';
import { Laval } from './Laval';
+import { MetropoleSavoie } from './MetropoleSavoie';
+import { Montpellier } from './Montpellier';
import { Mrn } from './Mrn';
import { Nm } from './Nm';
import { Occitanie } from './Occitanie';
+import { PaysBasque } from './PaysBasque';
import { Pdll } from './Pdll';
+import { Pdll2023 } from './Pdll2023';
+import { Pdll2024 } from './Pdll2024';
+import { PetrLunevilloisS12023 } from './PetrLunevilloisS12023';
import { Pmgf } from './Pmgf';
+import { Pmgf2023 } from './Pmgf2023';
+import { PmgfLate2023 } from './PmgfLate2023';
+import { SMTC } from './SMTC';
import { Smt } from './Smt';
+import { Smt2023 } from './Smt2023';
+import { Vitre } from './Vitre';
import { PolicyTemplateOne } from './unbound/PolicyTemplateOne';
import { PolicyTemplateThree } from './unbound/PolicyTemplateThree';
import { PolicyTemplateTwo } from './unbound/PolicyTemplateTwo';
-import { MetropoleSavoie } from './MetropoleSavoie';
-import { Smt2023 } from './Smt2023';
-import { LaRochelle } from './LaRochelle';
-import { PaysBasque } from './PaysBasque';
-import { Atmb } from './Atmb';
-import { Pmgf2023 } from './Pmgf2023';
-import { GrandPoitiers } from './GrandPoitiers';
-import { PmgfLate2023 } from './PmgfLate2023';
-import { Pdll2024 } from './Pdll2024';
-import { SMTC } from './SMTC';
-import { Cannes } from './Cannes';
export const policies: Map = new Map(
// disable prettier to avoid having it reformat to a single line
// this helps with git conflicts when modifying the list.
/* eslint-disable prettier/prettier */
[
+ Atmb,
+ Cannes,
+ Cotentin,
+ GrandPoitiers,
Idfm,
Lannion,
+ LaRochelle,
Laval,
+ MetropoleSavoie,
+ Montpellier,
Mrn,
Nm,
Occitanie,
+ PaysBasque,
Pdll,
+ Pdll2023,
+ Pdll2024,
+ PetrLunevilloisS12023,
Pmgf,
+ Pmgf2023,
+ PmgfLate2023,
PolicyTemplateOne,
PolicyTemplateThree,
PolicyTemplateTwo,
Smt,
- Montpellier,
- MetropoleSavoie,
Smt2023,
- Vitre,
- Pdll2023,
- Cotentin,
- LaRochelle,
- PaysBasque,
- Atmb,
- Pmgf2023,
- GrandPoitiers,
- PmgfLate2023,
- Pdll2024,
SMTC,
- Cannes
+ Vitre,
].map((h) => [h.id, h]),
/* eslint-enable prettier/prettier */
);