From 50fa508d54bfb696685a53527b60961340e2bbee Mon Sep 17 00:00:00 2001
From: Bastien DUMONT <bdumont@grandlyon.com>
Date: Wed, 17 Apr 2024 10:43:47 +0000
Subject: [PATCH] chore(ecogestures): adjust score calculation

---
 src/services/ecogesture.service.spec.ts | 110 ++++++++++++++----------
 src/services/ecogesture.service.ts      |   7 +-
 src/utils/date.spec.ts                  |  49 ++++++++++-
 src/utils/date.ts                       |  34 ++++++--
 src/utils/hash.spec.ts                  |   2 +-
 tests/__mocks__/ecogesturesData.mock.ts |   2 +-
 6 files changed, 145 insertions(+), 59 deletions(-)

diff --git a/src/services/ecogesture.service.spec.ts b/src/services/ecogesture.service.spec.ts
index 7661d0998..d002f5f57 100644
--- a/src/services/ecogesture.service.spec.ts
+++ b/src/services/ecogesture.service.spec.ts
@@ -1,6 +1,11 @@
 import { QueryResult } from 'cozy-client'
 import ecogestureData from 'db/ecogestureData.json'
-import { EquipmentType, IndividualOrCollective, WarmingType } from 'enums'
+import {
+  EquipmentType,
+  IndividualOrCollective,
+  Season,
+  WarmingType,
+} from 'enums'
 import { Ecogesture } from 'models'
 import { ProfileEcogesture } from 'models/profileEcogesture.model'
 import mockClient from 'tests/__mocks__/client.mock'
@@ -14,7 +19,7 @@ import {
 } from 'tests/__mocks__/ecogesturesData.mock'
 import { mockProfileEcogesture } from 'tests/__mocks__/profileEcogesture.mock'
 import { getError } from 'tests/__mocks__/testUtils'
-import { SUMMER_MONTH_END, SUMMER_MONTH_START } from 'utils/date'
+import * as dateUtils from 'utils/date'
 import { hashFile } from 'utils/hash'
 import EcogestureService from './ecogesture.service'
 
@@ -49,10 +54,6 @@ const mockQueryResultEmpty: QueryResult<Ecogesture[]> = {
   skip: 0,
 }
 
-const currentMonth = new Date().getMonth() + 1
-const isSummerOrSpring =
-  currentMonth >= SUMMER_MONTH_START && currentMonth <= SUMMER_MONTH_END
-
 describe('Ecogesture service', () => {
   const ecogestureService = new EcogestureService(mockClient)
   describe('getAllEcogestures', () => {
@@ -198,56 +199,75 @@ describe('Ecogesture service', () => {
   })
 
   describe('getEcogestureListByProfile', () => {
-    it('should return ecogesture list according to profile ecogesture, sorted and filtered, depending on the current season (WINTER/SUMMER)', async () => {
-      const mockProfileEcogestureFull: ProfileEcogesture = {
-        ...mockProfileEcogesture,
-        equipments: [EquipmentType.WASHING_MACHINE, EquipmentType.DISHWASHER],
-      }
-
+    const mockProfileEcogestureFull: ProfileEcogesture = {
+      ...mockProfileEcogesture,
+      equipments: [
+        EquipmentType.WASHING_MACHINE,
+        EquipmentType.DISHWASHER,
+        EquipmentType.AIR_CONDITIONING,
+      ],
+    }
+    it('should return ecogesture list according to profile ecogesture, sorted and filtered, for SUMMER season', async () => {
       mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures)
-
+      jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.SUMMER)
       const result = await ecogestureService.getEcogestureListByProfile(
         mockProfileEcogestureFull
       )
-
-      expect(result.length).toBe(2)
-      if (isSummerOrSpring) {
-        // eslint-disable-next-line jest/no-conditional-expect
-        expect(result[1]).toBe(mockedEcogesturesData[0])
-      } else {
-        // eslint-disable-next-line jest/no-conditional-expect
-        expect(result[0]).toBe(mockedEcogesturesData[0])
-      }
+      expect(result[0]).toBe(mockedEcogesturesData[1])
+      expect(result[1]).toBe(mockedEcogesturesData[2])
+      expect(result[2]).toBe(mockedEcogesturesData[0])
+    })
+    it('should return ecogesture list according to profile ecogesture, sorted and filtered, for WINTER season', async () => {
+      mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures)
+      jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.WINTER)
+      const result = await ecogestureService.getEcogestureListByProfile(
+        mockProfileEcogestureFull
+      )
+      expect(result[0]).toBe(mockedEcogesturesData[0])
+      expect(result[1]).toBe(mockedEcogesturesData[2])
+      expect(result[2]).toBe(mockedEcogesturesData[1])
+    })
+    it('should return ecogesture list according to profile ecogesture, sorted and filtered, for NO season', async () => {
+      mockClient.query.mockResolvedValueOnce(mockQueryResultMockedEcogestures)
+      jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(null)
+      const result = await ecogestureService.getEcogestureListByProfile(
+        mockProfileEcogestureFull
+      )
+      expect(result[0]).toBe(mockedEcogesturesData[0])
+      expect(result[1]).toBe(mockedEcogesturesData[1])
+      expect(result[2]).toBe(mockedEcogesturesData[2])
     })
   })
 
   describe('calculateScore', () => {
-    describe('during summer season', () => {
-      if (isSummerOrSpring) {
-        it('should return correct scores for each ecogesture', () => {
-          const scores = mockedEcogesturesData.map(ecogesture =>
-            ecogestureService.calculateScore(ecogesture)
-          )
-
-          expect(scores[0]).toBe(0)
-          expect(scores[1]).toBe(8)
-          expect(scores[2]).toBe(1)
-        })
-      }
+    it('should return correct scores for each ecogesture for SUMMER season', () => {
+      jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.SUMMER)
+      const scores = mockedEcogesturesData.map(ecogesture =>
+        ecogestureService.calculateScore(ecogesture)
+      )
+      expect(scores[0]).toBe(0)
+      expect(scores[1]).toBe(8)
+      expect(scores[2]).toBe(1)
     })
 
-    describe('during winter season', () => {
-      if (!isSummerOrSpring) {
-        it('should return correct scores for each ecogesture', () => {
-          const scores = mockedEcogesturesData.map(ecogesture =>
-            ecogestureService.calculateScore(ecogesture)
-          )
+    it('should return correct scores for each ecogesture for WINTER season', () => {
+      jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(Season.WINTER)
+      const scores = mockedEcogesturesData.map(ecogesture =>
+        ecogestureService.calculateScore(ecogesture)
+      )
+      expect(scores[0]).toBe(8)
+      expect(scores[1]).toBe(0)
+      expect(scores[2]).toBe(1)
+    })
 
-          expect(scores[0]).toBe(8)
-          expect(scores[1]).toBe(0)
-          expect(scores[2]).toBe(1)
-        })
-      }
+    it('should return correct scores for each ecogesture for NO season', () => {
+      jest.spyOn(dateUtils, 'getCurrentSeason').mockReturnValue(null)
+      const scores = mockedEcogesturesData.map(ecogesture =>
+        ecogestureService.calculateScore(ecogesture)
+      )
+      expect(scores[0]).toBe(7)
+      expect(scores[1]).toBe(7)
+      expect(scores[2]).toBe(1)
     })
   })
 
diff --git a/src/services/ecogesture.service.ts b/src/services/ecogesture.service.ts
index c03e9950f..819959ca2 100644
--- a/src/services/ecogesture.service.ts
+++ b/src/services/ecogesture.service.ts
@@ -128,16 +128,17 @@ export default class EcogestureService {
     const score = ecogesture.efficiency * 2 - ecogesture.difficulty
 
     const currentSeason = getCurrentSeason()
-    const oppositeSeason = getOppositeSeason(currentSeason)
 
     if (ecogesture.season !== Season.NONE) {
       if (ecogesture.season === currentSeason) {
         return score + 1
-      } else if (ecogesture.season === oppositeSeason) {
+      } else if (
+        currentSeason &&
+        ecogesture.season === getOppositeSeason(currentSeason)
+      ) {
         return 0
       }
     }
-
     return score
   }
 
diff --git a/src/utils/date.spec.ts b/src/utils/date.spec.ts
index a1028add5..1211870ea 100644
--- a/src/utils/date.spec.ts
+++ b/src/utils/date.spec.ts
@@ -1,4 +1,4 @@
-import { FluidType, TimeStep } from 'enums'
+import { FluidType, Season, TimeStep } from 'enums'
 import { DateTime } from 'luxon'
 import { Dataload } from 'models'
 import { graphData } from 'tests/__mocks__/chartData.mock'
@@ -7,6 +7,7 @@ import {
   convertDateToMonthYearString,
   convertDateToShortDateString,
   getActualAnalysisDate,
+  getCurrentSeason,
   getLagDays,
   isLastDateReached,
   isLastPeriodReached,
@@ -436,4 +437,50 @@ describe('date utils', () => {
       expect(result).toEqual(mockDate)
     })
   })
+
+  describe('getCurrentSeason test', () => {
+    beforeEach(() => {
+      jest.clearAllMocks()
+    })
+    it('should return Season.SUMMER if the current month is between summer dates', () => {
+      const now = DateTime.local().setZone('utc', {
+        keepLocalTime: true,
+      })
+      jest
+        .spyOn(DateTime, 'local')
+        .mockReturnValue(now.set({ month: 9, day: 21 }))
+      const currentSeason = getCurrentSeason()
+      expect(currentSeason).toEqual(Season.SUMMER)
+    })
+    it('should return Season.WINTER if the current month is between winter dates', () => {
+      const now = DateTime.local().setZone('utc', {
+        keepLocalTime: true,
+      })
+      jest
+        .spyOn(DateTime, 'local')
+        .mockReturnValue(now.set({ month: 3, day: 31 }))
+      const currentSeason = getCurrentSeason()
+      expect(currentSeason).toEqual(Season.WINTER)
+    })
+    it('should return null if the current month is between summer and winter dates', () => {
+      const now = DateTime.local().setZone('utc', {
+        keepLocalTime: true,
+      })
+      jest
+        .spyOn(DateTime, 'local')
+        .mockReturnValue(now.set({ month: 10, day: 2, year: 2024 }))
+      const currentSeason = getCurrentSeason()
+      expect(currentSeason).toBeNull()
+    })
+    it('should return null if the current month is between winter and summer dates', () => {
+      const now = DateTime.local().setZone('utc', {
+        keepLocalTime: true,
+      })
+      jest
+        .spyOn(DateTime, 'local')
+        .mockReturnValue(now.set({ month: 4, day: 20 }))
+      const currentSeason = getCurrentSeason()
+      expect(currentSeason).toBeNull()
+    })
+  })
 })
diff --git a/src/utils/date.ts b/src/utils/date.ts
index 764ea5c0e..434975bc1 100644
--- a/src/utils/date.ts
+++ b/src/utils/date.ts
@@ -3,8 +3,17 @@ import { DateTime } from 'luxon'
 import { Dataload } from 'models'
 import { getMonthNameWithPrep } from './utils'
 
-export const SUMMER_MONTH_START = 3
-export const SUMMER_MONTH_END = 8
+/** Between 21st of June and 20th of September */
+export const SUMMER_WEEK_DATES = {
+  start: 25,
+  end: 38,
+} as const
+
+/** Between 31st of October and 30th of March */
+const WINTER_WEEK_DATES = {
+  start: 44,
+  end: 13,
+} as const
 
 export function compareDates(dateA: DateTime, dateB: DateTime) {
   return dateA < dateB ? -1 : 1
@@ -150,16 +159,25 @@ export const getActualAnalysisDate = (): DateTime => {
 
 /**
  * Determines the current season based on the month of the year.
- * @returns {Season} Returns Season.SUMMER if the current month is between March and August, and Season.WINTER for the rest of the months.
+ * @returns
+ * - Returns Season.SUMMER if the current month is between summer dates,
+ * - Returns Season.WINTER if the current month is between winter dates,
+ * - Otherwise returns null.
  */
-export function getCurrentSeason(): Season {
-  const currentMonth = new Date().getMonth() + 1
-
-  if (currentMonth >= SUMMER_MONTH_START && currentMonth <= SUMMER_MONTH_END) {
+export function getCurrentSeason() {
+  const weekNumber = DateTime.local().weekNumber
+  if (
+    weekNumber >= SUMMER_WEEK_DATES.start &&
+    weekNumber <= SUMMER_WEEK_DATES.end
+  ) {
     return Season.SUMMER
-  } else {
+  } else if (
+    weekNumber >= WINTER_WEEK_DATES.start ||
+    weekNumber <= WINTER_WEEK_DATES.end
+  ) {
     return Season.WINTER
   }
+  return null
 }
 
 export function getOppositeSeason(currentSeason: Season): Season {
diff --git a/src/utils/hash.spec.ts b/src/utils/hash.spec.ts
index 54472af2e..cd4a4d02c 100644
--- a/src/utils/hash.spec.ts
+++ b/src/utils/hash.spec.ts
@@ -5,7 +5,7 @@ describe('hash utils test', () => {
   describe('hashFile test', () => {
     it('should return the correct hash of the file', () => {
       const result = hashFile(mockedEcogesturesData)
-      expect(result).toBe('cfe97043548a13a186b9545388804615c4feeb6f')
+      expect(result).toBe('bc5a72e07c44368c1841021ef0d42d9ed61de250')
     })
   })
 })
diff --git a/tests/__mocks__/ecogesturesData.mock.ts b/tests/__mocks__/ecogesturesData.mock.ts
index 9cbfc7b6d..c72c461c3 100644
--- a/tests/__mocks__/ecogesturesData.mock.ts
+++ b/tests/__mocks__/ecogesturesData.mock.ts
@@ -64,7 +64,7 @@ export const mockedEcogesturesData: Ecogesture[] = [
       "Utilisez la température la plus basse possible : de nombreux produits nettoyants sont efficaces à froid et un cycle à 90 °C consomme 3 fois plus d'énergie qu'un lavage à 40 °C. En effet, 80 % de l'énergie consommée par un lave-linge ou un lave-vaisselle sert au chauffage de l'eau ! Que ce soit pour la vaisselle ou le linge, les programmes de lavage intensif consomment jusqu'à 40 % de plus. Si possible, rincez à l'eau froide : la température de rinçage n'a pas d'effet sur le nettoyage du linge ou de la vaisselle. Attention cependant avec les tissus qui peuvent rétrécir : ce qui fait rétrécir, c'est le passage d'une température à une autre. Mieux vaut alors faire le cycle complet à l'eau froide pour les premiers lavages de tissus sensibles. Pour du linge ou de la vaisselle peu sales, utilisez la touche \"Eco\". Elle réduit la température de lavage et allonge sa durée (c’est le chauffage de l’eau qui consomme le plus). Vous économiserez jusqu’à 45 % par rapport aux cycles longs. Néanmoins, pour vous prémunir contre les bouchons de graisse dans les canalisations, faites quand même un cycle à chaud une fois par mois environ.",
     longName:
       'J’utilise le plus souvent les cycles courts à basse température pour laver le linge et la vaisselle.',
-    shortName: 'Accelerateur de particules',
+    shortName: 'Accélérateur de particules',
     usage: Usage.ELECTRICITY_SPECIFIC,
     impactLevel: 2,
     efficiency: 1,
-- 
GitLab