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 */ );