From c64756f1774957567c93cafa91da99034611d5a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marl=C3=A8ne=20SIMONDANT?= <msimondant@grandlyon.com>
Date: Tue, 30 Apr 2024 07:10:05 +0000
Subject: [PATCH] feat(analysis): add monthly average temperature comparison

---
 manifest.webapp                               |   4 +
 src/assets/icons/ico/exclamationMark.svg      |  28 ++++
 src/assets/png/temperatures/cold.svg          |  36 +++++
 src/assets/png/temperatures/hot.svg           |  48 +++++++
 .../Analysis/Comparison/Comparison.tsx        |   3 +-
 .../Comparison/TemperatureComparison.spec.tsx |  75 +++++++++++
 .../Comparison/TemperatureComparison.tsx      | 111 ++++++++++++++++
 .../TemperatureComparisonModal.spec.tsx       |  34 +++++
 .../TemperatureComparisonModal.tsx            |  61 +++++++++
 .../TemperatureComparisonModal.spec.tsx.snap  |  92 +++++++++++++
 .../temperatureComparisonModal.scss           |  25 ++++
 .../TemperatureComparison.spec.tsx.snap       | 124 ++++++++++++++++++
 .../Comparison/temperatureComparison.scss     |  50 +++++++
 .../remote/org.ecolyo.avg-temperature.ts      |   2 +
 src/locales/fr.json                           |  12 ++
 src/models/avgTemperature.model.ts            |  13 ++
 src/models/dju.model.ts                       |   2 +-
 src/models/index.ts                           |   1 +
 src/services/consumption.service.ts           |  41 ++++++
 src/services/profileType.service.spec.ts      |   4 +-
 src/services/profileType.service.ts           |   2 +-
 src/styles/base/_typo-variables.scss          |  19 ++-
 22 files changed, 779 insertions(+), 8 deletions(-)
 create mode 100644 src/assets/icons/ico/exclamationMark.svg
 create mode 100644 src/assets/png/temperatures/cold.svg
 create mode 100644 src/assets/png/temperatures/hot.svg
 create mode 100644 src/components/Analysis/Comparison/TemperatureComparison.spec.tsx
 create mode 100644 src/components/Analysis/Comparison/TemperatureComparison.tsx
 create mode 100644 src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx
 create mode 100644 src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx
 create mode 100644 src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap
 create mode 100644 src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss
 create mode 100644 src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap
 create mode 100644 src/components/Analysis/Comparison/temperatureComparison.scss
 create mode 100644 src/doctypes/remote/org.ecolyo.avg-temperature.ts
 create mode 100644 src/models/avgTemperature.model.ts

diff --git a/manifest.webapp b/manifest.webapp
index 37f256c9d..9b8765136 100644
--- a/manifest.webapp
+++ b/manifest.webapp
@@ -243,6 +243,10 @@
       "type": "org.ecolyo.dju_v3",
       "verbs": ["GET"]
     },
+    "ecolyo-avg-temperature": {
+      "type": "org.ecolyo.avg-temperature",
+      "verbs": ["GET"]
+    },
     "dacc": {
       "type": "cc.cozycloud.dacc_v2",
       "verbs": ["ALL"]
diff --git a/src/assets/icons/ico/exclamationMark.svg b/src/assets/icons/ico/exclamationMark.svg
new file mode 100644
index 000000000..987c0f422
--- /dev/null
+++ b/src/assets/icons/ico/exclamationMark.svg
@@ -0,0 +1,28 @@
+<svg
+   viewBox="0 0 16 16"
+   version="1.1"
+    fill="none" 
+    xmlns="http://www.w3.org/2000/svg">
+  <circle
+     cx="7.8000002"
+     cy="7.8000002"
+     r="7.3000002"
+     id="circle2"
+     style="fill:none;" />
+  <rect
+     x="7.1999998"
+     y="7.1999998"
+     width="1.2"
+     height="5.4000001"
+     rx="0.60000002"
+     id="rect4"
+     style="fill:#1b1c22" />
+  <rect
+     x="7.1999998"
+     y="3.5999904"
+     width="1.2"
+     height="1.8"
+     rx="0.60000002"
+     id="rect6"
+     style="fill:#1b1c22" />
+</svg>
diff --git a/src/assets/png/temperatures/cold.svg b/src/assets/png/temperatures/cold.svg
new file mode 100644
index 000000000..e466a2bbe
--- /dev/null
+++ b/src/assets/png/temperatures/cold.svg
@@ -0,0 +1,36 @@
+<svg width="288" height="60" viewBox="0 0 288 60" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_15704_1893)">
+<g opacity="0.5" filter="url(#filter0_d_15704_1893)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M256.694 67.2605C256.671 67.2607 256.647 67.2608 256.623 67.2608C251.596 67.2608 247.521 62.8458 247.521 57.3997C247.521 51.9536 251.596 47.5387 256.623 47.5387C256.672 47.5387 256.721 47.5391 256.77 47.5399C257.884 38.9789 265.205 32.3674 274.07 32.3674C280.755 32.3674 286.562 36.1277 289.491 41.6485C290.15 41.531 290.826 41.4699 291.516 41.4699C298.219 41.4699 303.653 47.2433 303.653 54.3651C303.653 61.4869 298.219 67.2602 291.516 67.2603V67.2605H256.694Z" fill="#4D5C6E"/>
+</g>
+<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M195.958 67.2605C195.982 67.2607 196.005 67.2608 196.029 67.2608C201.057 67.2608 205.132 62.8458 205.132 57.3997C205.132 51.9536 201.057 47.5387 196.029 47.5387C195.98 47.5387 195.931 47.5391 195.882 47.5399C194.768 38.9789 187.447 32.3674 178.583 32.3674C171.897 32.3674 166.09 36.1277 163.161 41.6485C162.503 41.531 161.826 41.4699 161.136 41.4699C154.433 41.4699 149 47.2433 149 54.3651C149 61.4869 154.433 67.2602 161.136 67.2603V67.2605H195.958Z" fill="#4D5C6E"/>
+<g filter="url(#filter1_d_15704_1893)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M189.232 77.5223C189.207 77.5224 189.182 77.5225 189.157 77.5225C180.582 77.5225 173.63 69.9913 173.63 60.7011C173.63 51.4109 180.582 43.8797 189.157 43.8797C189.241 43.8797 189.325 43.8804 189.408 43.8819C191.309 29.2782 203.797 18 218.919 18C230.323 18 240.229 24.4144 245.226 33.8322C246.349 33.6318 247.502 33.5275 248.679 33.5275C260.113 33.5275 269.382 43.376 269.382 55.5247C269.382 67.6733 260.113 77.5218 248.68 77.5219V77.5223H189.232Z" fill="#4D5C6E"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_d_15704_1893" x="225.021" y="17.8674" width="101.132" height="79.8933" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="8"/>
+<feGaussianBlur stdDeviation="11.25"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1893"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1893" result="shape"/>
+</filter>
+<filter id="filter1_d_15704_1893" x="151.13" y="3.5" width="140.753" height="104.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="8"/>
+<feGaussianBlur stdDeviation="11.25"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1893"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1893" result="shape"/>
+</filter>
+<clipPath id="clip0_15704_1893">
+<rect width="288" height="60" rx="4" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/src/assets/png/temperatures/hot.svg b/src/assets/png/temperatures/hot.svg
new file mode 100644
index 000000000..5e817db16
--- /dev/null
+++ b/src/assets/png/temperatures/hot.svg
@@ -0,0 +1,48 @@
+<svg width="288" height="60" viewBox="0 0 288 60" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_15704_1892)">
+<g filter="url(#filter0_d_15704_1892)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M251.15 67.2604C251.125 67.2605 251.1 67.2606 251.075 67.2606C244.843 67.2606 239.79 61.787 239.79 55.0349C239.79 48.2828 244.843 42.8092 251.075 42.8092C251.136 42.8092 251.197 42.8097 251.257 42.8107C252.639 32.1969 261.715 24 272.705 24C280.994 24 288.194 28.662 291.825 35.5067C292.641 35.3611 293.48 35.2853 294.335 35.2853C302.645 35.2853 309.382 42.4432 309.382 51.2728C309.382 60.1023 302.646 67.26 294.336 67.2603V67.2604H251.15Z" fill="white"/>
+</g>
+<g filter="url(#filter1_d_15704_1892)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M164.174 73.893C164.15 73.8932 164.126 73.8933 164.102 73.8933C159.075 73.8933 155 69.4784 155 64.0323C155 58.5862 159.075 54.1712 164.102 54.1712C164.152 54.1712 164.201 54.1717 164.25 54.1725C165.364 45.6115 172.684 39 181.549 39C188.234 39 194.042 42.7602 196.971 48.2811C197.629 48.1636 198.306 48.1024 198.995 48.1024C205.698 48.1024 211.132 53.8758 211.132 60.9977C211.132 68.1194 205.698 73.8928 198.996 73.8929V73.893H164.174Z" fill="white" fill-opacity="0.35" shape-rendering="crispEdges"/>
+</g>
+<g filter="url(#filter2_d_15704_1892)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M200.602 77.5223C200.577 77.5224 200.552 77.5225 200.527 77.5225C191.952 77.5225 185 69.9913 185 60.7011C185 51.4109 191.952 43.8797 200.527 43.8797C200.611 43.8797 200.695 43.8804 200.778 43.8819C202.679 29.2782 215.167 18 230.289 18C241.693 18 251.599 24.4144 256.596 33.8322C257.719 33.6318 258.873 33.5275 260.049 33.5275C271.483 33.5275 280.753 43.376 280.753 55.5247C280.753 67.6733 271.484 77.5218 260.05 77.5219V77.5223H200.602Z" fill="white"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_d_15704_1892" x="217.29" y="9.5" width="114.592" height="88.2606" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="8"/>
+<feGaussianBlur stdDeviation="11.25"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1892"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1892" result="shape"/>
+</filter>
+<filter id="filter1_d_15704_1892" x="132.5" y="24.5" width="101.132" height="79.8933" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="8"/>
+<feGaussianBlur stdDeviation="11.25"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1892"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1892" result="shape"/>
+</filter>
+<filter id="filter2_d_15704_1892" x="162.5" y="3.5" width="140.753" height="104.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
+<feOffset dy="8"/>
+<feGaussianBlur stdDeviation="11.25"/>
+<feComposite in2="hardAlpha" operator="out"/>
+<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
+<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_15704_1892"/>
+<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_15704_1892" result="shape"/>
+</filter>
+<clipPath id="clip0_15704_1892">
+<rect width="288" height="60" rx="4" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/src/components/Analysis/Comparison/Comparison.tsx b/src/components/Analysis/Comparison/Comparison.tsx
index c14165379..afa648f2e 100644
--- a/src/components/Analysis/Comparison/Comparison.tsx
+++ b/src/components/Analysis/Comparison/Comparison.tsx
@@ -11,6 +11,7 @@ import { setPeriod } from 'store/analysis/analysis.slice'
 import { setCurrentTimeStep, setShowCompare } from 'store/chart/chart.slice'
 import { useAppDispatch, useAppSelector } from 'store/hooks'
 import FluidPerformanceIndicator from './FluidPerformanceIndicator'
+import TemperatureComparison from './TemperatureComparison'
 import './comparison.scss'
 
 const Comparison = ({
@@ -157,7 +158,7 @@ const Comparison = ({
             <Loader />
           </div>
         )}
-
+        {!isLoading && <TemperatureComparison />}
         {/* Placeholder when no data is found */}
         {!isLoading &&
           fluidsWithData.length === 0 &&
diff --git a/src/components/Analysis/Comparison/TemperatureComparison.spec.tsx b/src/components/Analysis/Comparison/TemperatureComparison.spec.tsx
new file mode 100644
index 000000000..8d3383f30
--- /dev/null
+++ b/src/components/Analysis/Comparison/TemperatureComparison.spec.tsx
@@ -0,0 +1,75 @@
+import { act, render, screen, waitFor } from '@testing-library/react'
+import { userEvent } from '@testing-library/user-event'
+import { DateTime } from 'luxon'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { createMockEcolyoStore } from 'tests/__mocks__/store'
+import TemperatureComparison from './TemperatureComparison'
+
+const mockFetchAvgTemperature = jest.fn<Promise<number | null>, []>()
+jest.mock('services/consumption.service', () => {
+  return jest.fn(() => ({
+    fetchAvgTemperature: mockFetchAvgTemperature,
+  }))
+})
+
+jest
+  .spyOn(DateTime.prototype, 'toLocaleString')
+  .mockReturnValue('novembre 2022')
+
+describe('TemperatureComparison component', () => {
+  const store = createMockEcolyoStore()
+  afterEach(() => {
+    jest.clearAllMocks()
+  })
+  it('should be rendered correctly with hot result', async () => {
+    mockFetchAvgTemperature.mockResolvedValueOnce(1)
+    mockFetchAvgTemperature.mockResolvedValueOnce(2)
+    const { container } = render(
+      <Provider store={store}>
+        <TemperatureComparison />
+      </Provider>
+    )
+    await waitFor(() => null, { container })
+    expect(container).toMatchSnapshot()
+  })
+  it('should be rendered correctly with cold result', async () => {
+    mockFetchAvgTemperature.mockResolvedValueOnce(2)
+    mockFetchAvgTemperature.mockResolvedValueOnce(1)
+    const { container } = render(
+      <Provider store={store}>
+        <TemperatureComparison />
+      </Provider>
+    )
+    await waitFor(() => null, { container })
+    expect(container).toMatchSnapshot()
+  })
+  it('should be rendered correctly with no data', async () => {
+    mockFetchAvgTemperature.mockResolvedValueOnce(null)
+    mockFetchAvgTemperature.mockResolvedValueOnce(0)
+    const { container } = render(
+      <Provider store={store}>
+        <TemperatureComparison />
+      </Provider>
+    )
+    await waitFor(() => null, { container })
+    expect(container).toMatchSnapshot()
+  })
+
+  it('should open the modal', async () => {
+    mockFetchAvgTemperature.mockResolvedValueOnce(1)
+    mockFetchAvgTemperature.mockResolvedValueOnce(2)
+    const { container } = render(
+      <Provider store={store}>
+        <TemperatureComparison />
+      </Provider>
+    )
+    await waitFor(() => container)
+    await act(async () => {
+      await userEvent.click(
+        screen.getByLabelText('analysis.temperature_comparison.info_button')
+      )
+    })
+    expect(screen.getByRole('dialog')).toBeInTheDocument()
+  })
+})
diff --git a/src/components/Analysis/Comparison/TemperatureComparison.tsx b/src/components/Analysis/Comparison/TemperatureComparison.tsx
new file mode 100644
index 000000000..393df4a98
--- /dev/null
+++ b/src/components/Analysis/Comparison/TemperatureComparison.tsx
@@ -0,0 +1,111 @@
+import ExclamationMarkIcon from 'assets/icons/ico/exclamationMark.svg'
+import StyledIconButton from 'components/CommonKit/IconButton/StyledIconButton'
+import { useClient } from 'cozy-client'
+import { useI18n } from 'cozy-ui/transpiled/react/I18n'
+import React, { useEffect, useMemo, useState } from 'react'
+import ConsumptionService from 'services/consumption.service'
+import { useAppSelector } from 'store/hooks'
+import TemperatureComparisonModal from './TemperatureComparisonModal/TemperatureComparisonModal'
+import './temperatureComparison.scss'
+
+const TemperatureComparison = () => {
+  const { t } = useI18n()
+  const client = useClient()
+  const { period, analysisMonth } = useAppSelector(
+    state => state.ecolyo.analysis
+  )
+
+  const [temperatureDifference, setTemperatureDifference] = useState<string>()
+  const [positive, setPositive] = useState<boolean>(true)
+  const [openTemperatureComparisonModal, setOpenTemperatureComparisonModal] =
+    useState<boolean>(false)
+  const consumptionService = useMemo(
+    () => new ConsumptionService(client),
+    [client]
+  )
+  const comparisonDates = useMemo(() => {
+    const endMonth = analysisMonth.minus({ month: 1 }).startOf('month')
+    return {
+      startMonth: endMonth
+        .minus({ month: period === 'month' ? 1 : 12 })
+        .startOf('month'),
+      endMonth: endMonth,
+    }
+  }, [analysisMonth, period])
+
+  useEffect(() => {
+    async function handleTemperatureComparison() {
+      const startMonthTemperature =
+        await consumptionService.fetchAvgTemperature(
+          comparisonDates.startMonth.year,
+          comparisonDates.startMonth.month
+        )
+
+      const endMonthTemperature = await consumptionService.fetchAvgTemperature(
+        comparisonDates.endMonth.year,
+        comparisonDates.endMonth.month
+      )
+
+      if (startMonthTemperature !== null && endMonthTemperature !== null) {
+        const temperatureDifference =
+          endMonthTemperature - startMonthTemperature
+        setTemperatureDifference(temperatureDifference.toFixed(1))
+        setPositive(temperatureDifference >= 0)
+      }
+    }
+    handleTemperatureComparison()
+  }, [
+    consumptionService,
+    comparisonDates.endMonth.month,
+    comparisonDates.endMonth.year,
+    comparisonDates.startMonth.month,
+    comparisonDates.startMonth.year,
+  ])
+
+  return (
+    <>
+      {temperatureDifference && (
+        <div className={`temperatureComparison ${positive ? 'hot' : 'cold'}`}>
+          <div className="tc-content">
+            <div>
+              <span className="text-28-bold">
+                {positive ? '+' : ''}
+                {temperatureDifference}&nbsp;
+              </span>
+              <span className="text-18">
+                {t('analysis.temperature_comparison.unit')}
+              </span>
+            </div>
+            <div>
+              <span className="tc-text text-12">
+                {t('analysis.temperature_comparison.comparison')}
+              </span>
+              <span className="tc-period text-12-bold">
+                &nbsp;
+                {comparisonDates.startMonth.toLocaleString({
+                  month: 'long',
+                  year: 'numeric',
+                })}
+              </span>
+            </div>
+          </div>
+
+          <StyledIconButton
+            icon={ExclamationMarkIcon}
+            sized={16}
+            onClick={() => setOpenTemperatureComparisonModal(true)}
+            aria-label={t('analysis.temperature_comparison.info_button')}
+            className="info-icon"
+          />
+
+          <TemperatureComparisonModal
+            open={openTemperatureComparisonModal}
+            handleCloseClick={() => setOpenTemperatureComparisonModal(false)}
+          />
+        </div>
+      )}
+    </>
+  )
+}
+
+export default TemperatureComparison
diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx
new file mode 100644
index 000000000..783e566a3
--- /dev/null
+++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.spec.tsx
@@ -0,0 +1,34 @@
+import { act, render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { createMockEcolyoStore } from 'tests/__mocks__/store'
+import TemperatureComparisonModal from './TemperatureComparisonModal'
+
+describe('TemperatureComparisonModal component', () => {
+  const store = createMockEcolyoStore()
+  it('should be rendered correctly', () => {
+    const { baseElement } = render(
+      <Provider store={store}>
+        <TemperatureComparisonModal open={true} handleCloseClick={jest.fn()} />
+      </Provider>
+    )
+    expect(baseElement).toMatchSnapshot()
+  })
+
+  it('should close modal', async () => {
+    const mockHandleClose = jest.fn()
+    render(
+      <Provider store={store}>
+        <TemperatureComparisonModal
+          open={true}
+          handleCloseClick={mockHandleClose}
+        />
+      </Provider>
+    )
+    await act(async () => {
+      await userEvent.click(screen.getAllByRole('button')[0])
+    })
+    expect(mockHandleClose).toHaveBeenCalledTimes(1)
+  })
+})
diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx
new file mode 100644
index 000000000..b165c9936
--- /dev/null
+++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/TemperatureComparisonModal.tsx
@@ -0,0 +1,61 @@
+import { IconButton } from '@material-ui/core'
+import Dialog from '@material-ui/core/Dialog'
+import CloseIcon from 'assets/icons/ico/close.svg'
+import { useI18n } from 'cozy-ui/transpiled/react/I18n'
+import Icon from 'cozy-ui/transpiled/react/Icon'
+import React from 'react'
+import { useAppSelector } from 'store/hooks'
+import './temperatureComparisonModal.scss'
+
+interface TemperatureComparisonModalProps {
+  open: boolean
+  handleCloseClick: () => void
+}
+
+const TemperatureComparisonModal = ({
+  open,
+  handleCloseClick,
+}: TemperatureComparisonModalProps) => {
+  const { t } = useI18n()
+  const { period } = useAppSelector(state => state.ecolyo.analysis)
+
+  return (
+    <Dialog
+      open={open}
+      onClose={handleCloseClick}
+      aria-label={t('analysis.temperature_comparison.modal.title')}
+      classes={{
+        root: 'modal-root',
+        paper: 'modal-paper',
+      }}
+    >
+      <div className="modal-start-root">
+        <IconButton
+          aria-label={t('analysis.temperature_comparison.modal.close')}
+          className="modal-paper-close-button"
+          onClick={handleCloseClick}
+        >
+          <Icon icon={CloseIcon} size={18} />
+        </IconButton>
+        <div className="content">
+          <div className="text-20-bold subtitle">
+            {t('analysis.temperature_comparison.modal.title')}
+          </div>
+          <div>
+            <p className="text-15-bold">
+              {period === 'month'
+                ? t('analysis.temperature_comparison.modal.month_comparison')
+                : t('analysis.temperature_comparison.modal.year_comparison')}
+            </p>
+            <br />
+            <p className="text-15-bold">
+              {t('analysis.temperature_comparison.modal.data_info')}
+            </p>
+          </div>
+        </div>
+      </div>
+    </Dialog>
+  )
+}
+
+export default TemperatureComparisonModal
diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap b/src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap
new file mode 100644
index 000000000..19f0c60c8
--- /dev/null
+++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/__snapshots__/TemperatureComparisonModal.spec.tsx.snap
@@ -0,0 +1,92 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TemperatureComparisonModal component should be rendered correctly 1`] = `
+<body
+  style="padding-right: 0px; overflow: hidden;"
+>
+  <div
+    aria-hidden="true"
+  />
+  <div
+    aria-label="analysis.temperature_comparison.modal.title"
+    class="MuiDialog-root modal-root"
+    role="presentation"
+    style="position: fixed; z-index: 1300; right: 0px; bottom: 0px; top: 0px; left: 0px;"
+  >
+    <div
+      aria-hidden="true"
+      class="MuiBackdrop-root"
+      style="opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;"
+    />
+    <div
+      data-test="sentinelStart"
+      tabindex="0"
+    />
+    <div
+      class="MuiDialog-container MuiDialog-scrollPaper"
+      role="none presentation"
+      style="opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;"
+      tabindex="-1"
+    >
+      <div
+        class="MuiPaper-root MuiDialog-paper modal-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthSm MuiPaper-elevation24 MuiPaper-rounded"
+        role="dialog"
+      >
+        <div
+          class="modal-start-root"
+        >
+          <button
+            aria-label="analysis.temperature_comparison.modal.close"
+            class="MuiButtonBase-root MuiIconButton-root modal-paper-close-button"
+            tabindex="0"
+            type="button"
+          >
+            <span
+              class="MuiIconButton-label"
+            >
+              <svg
+                class="styles__icon___23x3R"
+                height="18"
+                width="18"
+              >
+                <use
+                  xlink:href="#test-file-stub"
+                />
+              </svg>
+            </span>
+            <span
+              class="MuiTouchRipple-root"
+            />
+          </button>
+          <div
+            class="content"
+          >
+            <div
+              class="text-20-bold subtitle"
+            >
+              analysis.temperature_comparison.modal.title
+            </div>
+            <div>
+              <p
+                class="text-15-bold"
+              >
+                analysis.temperature_comparison.modal.month_comparison
+              </p>
+              <br />
+              <p
+                class="text-15-bold"
+              >
+                analysis.temperature_comparison.modal.data_info
+              </p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div
+      data-test="sentinelEnd"
+      tabindex="0"
+    />
+  </div>
+</body>
+`;
diff --git a/src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss b/src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss
new file mode 100644
index 000000000..2375d9550
--- /dev/null
+++ b/src/components/Analysis/Comparison/TemperatureComparisonModal/temperatureComparisonModal.scss
@@ -0,0 +1,25 @@
+@import 'src/styles/base/color';
+
+.modal-start-root {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+
+  .content {
+    text-align: center;
+    padding: 1rem 0;
+    display: flex;
+    flex-direction: column;
+    gap: 2rem;
+    flex-grow: 1;
+
+    .subtitle {
+      color: $gold-shadow;
+    }
+
+    p {
+      color: $white;
+      margin: 0;
+    }
+  }
+}
diff --git a/src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap b/src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap
new file mode 100644
index 000000000..eac3ee09c
--- /dev/null
+++ b/src/components/Analysis/Comparison/__snapshots__/TemperatureComparison.spec.tsx.snap
@@ -0,0 +1,124 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`TemperatureComparison component should be rendered correctly with cold result 1`] = `
+<div>
+  <div
+    class="temperatureComparison cold"
+  >
+    <div
+      class="tc-content"
+    >
+      <div>
+        <span
+          class="text-28-bold"
+        >
+          -1.0
+           
+        </span>
+        <span
+          class="text-18"
+        >
+          analysis.temperature_comparison.unit
+        </span>
+      </div>
+      <div>
+        <span
+          class="tc-text text-12"
+        >
+          analysis.temperature_comparison.comparison
+        </span>
+        <span
+          class="tc-period text-12-bold"
+        >
+           
+          novembre 2022
+        </span>
+      </div>
+    </div>
+    <button
+      aria-label="analysis.temperature_comparison.info_button"
+      class="MuiButtonBase-root MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-2 info-icon"
+      tabindex="0"
+      type="button"
+    >
+      <span
+        class="MuiIconButton-label"
+      >
+        <svg
+          aria-hidden="true"
+          class="styles__icon___23x3R"
+          height="16"
+          width="16"
+        >
+          <use
+            xlink:href="#test-file-stub"
+          />
+        </svg>
+      </span>
+    </button>
+  </div>
+</div>
+`;
+
+exports[`TemperatureComparison component should be rendered correctly with hot result 1`] = `
+<div>
+  <div
+    class="temperatureComparison hot"
+  >
+    <div
+      class="tc-content"
+    >
+      <div>
+        <span
+          class="text-28-bold"
+        >
+          +
+          1.0
+           
+        </span>
+        <span
+          class="text-18"
+        >
+          analysis.temperature_comparison.unit
+        </span>
+      </div>
+      <div>
+        <span
+          class="tc-text text-12"
+        >
+          analysis.temperature_comparison.comparison
+        </span>
+        <span
+          class="tc-period text-12-bold"
+        >
+           
+          novembre 2022
+        </span>
+      </div>
+    </div>
+    <button
+      aria-label="analysis.temperature_comparison.info_button"
+      class="MuiButtonBase-root MuiIconButton-root WithStyles(ForwardRef(IconButton))-root-1 info-icon"
+      tabindex="0"
+      type="button"
+    >
+      <span
+        class="MuiIconButton-label"
+      >
+        <svg
+          aria-hidden="true"
+          class="styles__icon___23x3R"
+          height="16"
+          width="16"
+        >
+          <use
+            xlink:href="#test-file-stub"
+          />
+        </svg>
+      </span>
+    </button>
+  </div>
+</div>
+`;
+
+exports[`TemperatureComparison component should be rendered correctly with no data 1`] = `<div />`;
diff --git a/src/components/Analysis/Comparison/temperatureComparison.scss b/src/components/Analysis/Comparison/temperatureComparison.scss
new file mode 100644
index 000000000..ce4fc3e5e
--- /dev/null
+++ b/src/components/Analysis/Comparison/temperatureComparison.scss
@@ -0,0 +1,50 @@
+@import 'src/styles/base/color';
+@import 'src/styles/base/breakpoint';
+
+.temperatureComparison {
+  display: flex;
+  border: 1px solid $grey-dark;
+  border-radius: 4px;
+  padding: 4px 4px 8px 22px;
+  box-shadow: 0px 4px 16px 0px $black-shadow;
+  background: linear-gradient(180deg, #323339 0%, #25262b 100%);
+  background-position: bottom right;
+  background-repeat: no-repeat;
+  &.hot {
+    background-image: url('../../../assets/png/temperatures/hot.svg'),
+      linear-gradient(259deg, rgba(6, 29, 62, 0) 25.28%, #77aee0 121.36%),
+      radial-gradient(
+        185.82% 146.65% at 50% 79.83%,
+        rgba(6, 29, 62, 0) 0%,
+        #e0bc77 100%
+      );
+  }
+  &.cold {
+    background-image: url('../../../assets/png/temperatures/cold.svg'),
+      radial-gradient(
+        185.82% 146.65% at 50% 79.83%,
+        rgba(6, 29, 62, 0) 0%,
+        #77a3e0 100%
+      );
+  }
+
+  .tc-content {
+    text-align: left;
+    padding-top: 4px;
+    flex-grow: 1;
+    color: $white;
+    .tc-text {
+      font-weight: 200;
+    }
+    .tc-period {
+      font-weight: 900;
+    }
+  }
+
+  .info-icon {
+    padding: 0;
+    stroke: $white;
+    align-items: flex-start;
+    height: 100%;
+  }
+}
diff --git a/src/doctypes/remote/org.ecolyo.avg-temperature.ts b/src/doctypes/remote/org.ecolyo.avg-temperature.ts
new file mode 100644
index 000000000..ff3c5e7a4
--- /dev/null
+++ b/src/doctypes/remote/org.ecolyo.avg-temperature.ts
@@ -0,0 +1,2 @@
+export const REMOTE_ORG_ECOLYO_AVG_TEMPERATURE =
+  '/remote/org.ecolyo.avg-temperature'
diff --git a/src/locales/fr.json b/src/locales/fr.json
index ca5ec4912..32c7bb1ea 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -105,6 +105,18 @@
       "title": "Comparateur",
       "month_tab": "Comparer au mois dernier",
       "year_tab": "Comparer à l'année dernière"
+    },
+    "temperature_comparison": {
+      "unit": "°C",
+      "comparison": "par rapport à",
+      "info_button": "Information sur l'indice météo",
+      "modal": {
+        "title": "Indice météo",
+        "month_comparison": "Écart de température moyenne entre le mois observé et le mois précédent.",
+        "year_comparison": "Écart de température moyenne entre le mois observé et le même mois de l'année précédente.",
+        "data_info": "Données Météo France issues de la station météo Lyon Bron.",
+        "close": "Fermer la fenêtre"
+      }
     }
   },
   "analysis_error_modal": {
diff --git a/src/models/avgTemperature.model.ts b/src/models/avgTemperature.model.ts
new file mode 100644
index 000000000..74429edbb
--- /dev/null
+++ b/src/models/avgTemperature.model.ts
@@ -0,0 +1,13 @@
+export interface AvgTemperatureResult {
+  fields: string[]
+  layer_name: string
+  nb_results: number
+  table_href: string
+  values: AvgTemperatureMeasure[]
+}
+
+interface AvgTemperatureMeasure {
+  month: string
+  average_measurement: number
+  identifiant: string
+}
diff --git a/src/models/dju.model.ts b/src/models/dju.model.ts
index f5f43d648..57b9fdc64 100644
--- a/src/models/dju.model.ts
+++ b/src/models/dju.model.ts
@@ -1,7 +1,7 @@
 export interface DjuResult {
   fields: string[]
   layer_name: string
-  nb_result: number
+  nb_results: number
   table_href: string
   values: DjuMeasure[]
 }
diff --git a/src/models/index.ts b/src/models/index.ts
index 99412717d..e4f280922 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -1,6 +1,7 @@
 export * from './account.model'
 export * from './action.model'
 export * from './analysis.model'
+export * from './avgTemperature.model'
 export * from './challenge.model'
 export * from './chart.model'
 export * from './config.model'
diff --git a/src/services/consumption.service.ts b/src/services/consumption.service.ts
index daaf292f0..68551b7a8 100644
--- a/src/services/consumption.service.ts
+++ b/src/services/consumption.service.ts
@@ -1,9 +1,13 @@
+import * as Sentry from '@sentry/react'
 import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client'
 import { Doctype } from 'cozy-client/types/types'
+import logger from 'cozy-logger'
 import { ENEDIS_MINUTE_DOCTYPE } from 'doctypes'
+import { REMOTE_ORG_ECOLYO_AVG_TEMPERATURE } from 'doctypes/remote/org.ecolyo.avg-temperature'
 import { DataloadState, FluidType, TimeStep } from 'enums'
 import { DateTime } from 'luxon'
 import {
+  AvgTemperatureResult,
   Datachart,
   Dataload,
   DataloadEntity,
@@ -17,12 +21,16 @@ import ConsumptionFormatterService from 'services/consumptionFormatter.service'
 import ConsumptionValidatorService from 'services/consumptionValidator.service'
 import ConverterService from 'services/converter.service'
 import QueryRunnerService from 'services/queryRunner.service'
+import logApp from 'utils/logger'
+import { formatTwoDigits } from 'utils/utils'
 
 interface ISingleFluidChartData {
   chartData: Datachart | null
   chartFluid: FluidType
 }
 
+const logStack = logger.namespace('consumptionService')
+
 export default class ConsumptionDataManager {
   private readonly _client: Client
   private readonly _consumptionFormatterService: ConsumptionFormatterService
@@ -620,6 +628,39 @@ export default class ConsumptionDataManager {
     return data.data[0]
   }
 
+  /**
+   * Try to fetch average temperature from remote doctype
+   * @returns {Promise<number | null>} avg-temperature
+   */
+  public fetchAvgTemperature = async (
+    year: number,
+    month: number
+  ): Promise<number | null> => {
+    const bronStationId = '69123002'
+    const avgTemperatureDate = `${year}-${formatTwoDigits(month)}`
+    try {
+      const result: AvgTemperatureResult = await this._client
+        .getStackClient()
+        .fetchJSON(
+          'GET',
+          `${REMOTE_ORG_ECOLYO_AVG_TEMPERATURE}?identifiant=${bronStationId}&month=${avgTemperatureDate}`
+        )
+      if (result && result.nb_results !== 0) {
+        return result.values[0].average_measurement
+      } else {
+        throw new Error(
+          `No average temperature found for ${avgTemperatureDate}`
+        )
+      }
+    } catch (error) {
+      const errorMessage = `fetchAvgTemperature error : ${error}`
+      logStack('error', errorMessage)
+      logApp.error(errorMessage)
+      Sentry.captureException(error)
+      return null
+    }
+  }
+
   /**
    * Save one doc
    * @param {DataloadEntity} consumptionDoc - Doc to save
diff --git a/src/services/profileType.service.spec.ts b/src/services/profileType.service.spec.ts
index 0e8340734..cda9941ab 100644
--- a/src/services/profileType.service.spec.ts
+++ b/src/services/profileType.service.spec.ts
@@ -550,7 +550,7 @@ describe('ProfileType service', () => {
       const mockDjuResult: DjuResult = {
         fields: [],
         layer_name: '',
-        nb_result: 1,
+        nb_results: 1,
         table_href: '',
         values: [
           {
@@ -568,7 +568,7 @@ describe('ProfileType service', () => {
       const mockDjuResult: DjuResult = {
         fields: [],
         layer_name: '',
-        nb_result: 0,
+        nb_results: 0,
         table_href: '',
         values: [],
       }
diff --git a/src/services/profileType.service.ts b/src/services/profileType.service.ts
index 91aa35bba..8bd19f363 100644
--- a/src/services/profileType.service.ts
+++ b/src/services/profileType.service.ts
@@ -451,7 +451,7 @@ export default class ProfileTypeService {
       const result: DjuResult = await this._client
         .getStackClient()
         .fetchJSON('GET', `${REMOTE_ORG_ECOLYO_DJU}?month=${djuDate}`)
-      if (result && result.nb_result !== 0) {
+      if (result && result.nb_results !== 0) {
         return result.values[0].average_measurement
       } else {
         return heatingData.dju_average_by_month[month - 1]
diff --git a/src/styles/base/_typo-variables.scss b/src/styles/base/_typo-variables.scss
index 3e357c1bf..18bf2b8ee 100644
--- a/src/styles/base/_typo-variables.scss
+++ b/src/styles/base/_typo-variables.scss
@@ -1,5 +1,18 @@
 $text-font: Lato, sans-serif;
 
-$text-size: '10' 0.625rem, '13' 0.8125rem, '14' 0.875rem, '15' 0.938rem,
-  '16' 1rem, '18' 1.125rem, '19' 1.188rem, '20' 1.25rem, '21' 1.313rem,
-  '22' 1.375rem, '24' 1.5rem, '26' 1.625rem, '28' 1.75rem, '36' 2.25rem;
+$text-size:
+  '10' 0.625rem,
+  '12' 0.75rem,
+  '13' 0.8125rem,
+  '14' 0.875rem,
+  '15' 0.938rem,
+  '16' 1rem,
+  '18' 1.125rem,
+  '19' 1.188rem,
+  '20' 1.25rem,
+  '21' 1.313rem,
+  '22' 1.375rem,
+  '24' 1.5rem,
+  '26' 1.625rem,
+  '28' 1.75rem,
+  '36' 2.25rem;
-- 
GitLab