diff --git a/scripts/config.template.js b/scripts/config.template.js
index 5988a8518c26e209466edbd60a6d6dd19637c901..730db3441b0abe091627cb8c1de72f316e78e0cb 100644
--- a/scripts/config.template.js
+++ b/scripts/config.template.js
@@ -6,15 +6,15 @@ const authorization =
 const cookie =
   'cozysessid=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
 
-const startingdate = DateTime.local().plus({ days: -120 }).startOf('day')
+const startingDate = DateTime.local().plus({ days: -120 }).startOf('day')
 const endingDate = DateTime.local().plus({ days: -1 }).startOf('day')
 /** Elec starting date */
-const halfHourStartingdate = DateTime.local().plus({ days: -15 }).startOf('day')
+const halfHourStartingDate = DateTime.local().plus({ days: -15 }).startOf('day')
 
 module.exports = {
   authorization,
   cookie,
-  startingdate,
+  startingDate,
   endingDate,
-  halfHourStartingdate,
+  halfHourStartingDate,
 }
diff --git a/scripts/createConnections.js b/scripts/createConnections.js
index c857d3f6fac014027eb3f1af41151dd75c7a3e41..728531c3b77fe2b8c8aaeabd37294406c72348a5 100644
--- a/scripts/createConnections.js
+++ b/scripts/createConnections.js
@@ -23,6 +23,7 @@ const dataEnedisAccount = JSON.stringify({
   data: {
     consentId: 43,
     expirationDate: '2023-09-26',
+    offPeakHours: '22H00-6H00',
   },
   id: '70e68b8450cee09fe2f077610901094d',
   identifier: 'address',
diff --git a/scripts/createDayDataFiles.js b/scripts/createDayDataFiles.js
index 2159ee996e6ff1e157ee57e3972b8ca8d8817391..1302b0384c784baaba9e3df4b934989f1e2c5f3b 100644
--- a/scripts/createDayDataFiles.js
+++ b/scripts/createDayDataFiles.js
@@ -6,48 +6,25 @@ const fs = require('fs')
 const { DateTime } = require('luxon')
 const config = require('./config')
 
-function getRandomInt(min, max) {
-  const minValue = Math.ceil(min * 100)
-  const maxValue = Math.floor(max * 100)
-  // NOSONAR
-  const result =
-    (Math.floor(Math.random() * (maxValue - minValue)) + minValue) / 100 // NOSONAR
-  return result
+function getRandomNumber(min, max) {
+  // Generate a random float between min and max
+  let randomFloat = Math.random() * (max - min) + min
+  // Round to two decimal places
+  randomFloat = Math.round(randomFloat * 100) / 100
+  return randomFloat
 }
 
-const generateHalfAnHourData = (_startingdate, _endingDate, min, max) => {
-  let parsingDate
-
-  parsingDate = DateTime.local(
-    _startingdate.year,
-    _startingdate.month,
-    _startingdate.day,
+const generateHalfAnHourData = (startingDate, endingDate, min, max) => {
+  let parsingDate = DateTime.local(
+    startingDate.year,
+    startingDate.month,
+    startingDate.day,
     0,
     0
   )
-
   const halfAnHourDumpArray = []
-
-  let lastDay = parsingDate.day
-  const dayDumpArray = []
-
-  let lastMonth = parsingDate.month
-  const monthDumpArray = []
-
-  let lastYear = parsingDate.year
-  const yearDumpArray = []
-
-  let dailyLoad = 0
-  let monthlyLoad = 0
-  let yearlyLoad = 0
-
-  while (parsingDate <= _endingDate) {
-    const load = getRandomInt(min, max)
-
-    dailyLoad += load
-    monthlyLoad += load
-    yearlyLoad += load
-
+  while (parsingDate <= endingDate) {
+    const load = getRandomNumber(min, max)
     halfAnHourDumpArray.push({
       load: load,
       year: parsingDate.year,
@@ -56,107 +33,20 @@ const generateHalfAnHourData = (_startingdate, _endingDate, min, max) => {
       hour: parsingDate.hour,
       minute: parsingDate.minute,
     })
-
-    if (parsingDate.day !== lastDay) {
-      dayDumpArray.push({
-        load: Math.round(dailyLoad * 100) / 100,
-        year: lastYear,
-        month: lastMonth,
-        day: lastDay,
-        hour: 0,
-        minute: 0,
-      })
-      dailyLoad = 0
-      lastDay = parsingDate.day
-    }
-
-    if (parsingDate.month !== lastMonth) {
-      monthDumpArray.push({
-        load: Math.round(monthlyLoad * 100) / 100,
-        year: lastYear,
-        month: lastMonth,
-        day: 0,
-        hour: 0,
-        minute: 0,
-      })
-      monthlyLoad = 0
-      lastMonth = parsingDate.month
-    }
-
-    if (parsingDate.year !== lastYear) {
-      yearDumpArray.push({
-        load: Math.round(yearlyLoad * 100) / 100,
-        year: lastYear,
-        month: 1,
-        day: 1,
-        hour: 0,
-        minute: 0,
-      })
-      yearlyLoad = 0
-      lastYear = parsingDate.year
-    }
     parsingDate = parsingDate.plus({ minute: 30 })
   }
-
-  dayDumpArray.push({
-    load: Math.round(dailyLoad * 100) / 100,
-    year: lastYear,
-    month: lastMonth,
-    day: lastDay,
-    hour: 0,
-    minute: 0,
-  })
-
-  monthDumpArray.push({
-    load: Math.round(monthlyLoad * 100) / 100,
-    year: lastYear,
-    month: lastMonth,
-    day: 1,
-    hour: 0,
-    minute: 0,
-  })
-
-  yearDumpArray.push({
-    load: Math.round(yearlyLoad * 100) / 100,
-    year: lastYear,
-    month: 1,
-    day: 1,
-    hour: 0,
-    minute: 0,
-  })
-
-  return {
-    halfAnHourLoad: halfAnHourDumpArray,
-    dailyLoad: dayDumpArray,
-    monthlyLoad: monthDumpArray,
-    yearlyLoad: yearDumpArray,
-  }
+  return halfAnHourDumpArray
 }
 
-const generateData = (_startingdate, _endingDate, min, max) => {
+const generateData = (startingDate, endingDate, min, max) => {
   let parsingDate = DateTime.local(
-    _startingdate.year,
-    _startingdate.month,
-    _startingdate.day
+    startingDate.year,
+    startingDate.month,
+    startingDate.day
   )
-
   const dayDumpArray = []
-
-  let lastMonth = parsingDate.month
-  const monthDumpArray = []
-
-  let lastYear = parsingDate.year
-  const yearDumpArray = []
-
-  let monthlyLoad = 0
-  let yearlyLoad = 0
-
-  while (parsingDate <= _endingDate) {
-    const load = getRandomInt(min, max)
-
-    monthlyLoad += load
-    yearlyLoad += load
-
+  while (parsingDate <= endingDate) {
+    const load = getRandomNumber(min, max)
     dayDumpArray.push({
       load: load,
       year: parsingDate.year,
@@ -165,58 +55,51 @@ const generateData = (_startingdate, _endingDate, min, max) => {
       hour: 0,
       minute: 0,
     })
-
-    if (parsingDate.month !== lastMonth) {
-      monthDumpArray.push({
-        load: Math.round(monthlyLoad * 100) / 100,
-        year: lastYear,
-        month: lastMonth,
-        day: 0,
-        hour: 0,
-        minute: 0,
-      })
-      monthlyLoad = 0
-      lastMonth = parsingDate.month
-    }
-
-    if (parsingDate.year !== lastYear) {
-      yearDumpArray.push({
-        load: Math.round(yearlyLoad * 100) / 100,
-        year: lastYear,
-        month: 1,
-        day: 1,
-        hour: 0,
-        minute: 0,
-      })
-      yearlyLoad = 0
-      lastYear = parsingDate.year
-    }
     parsingDate = parsingDate.plus({ days: 1 })
   }
+  return dayDumpArray
+}
 
-  monthDumpArray.push({
-    load: Math.round(monthlyLoad * 100) / 100,
-    year: lastYear,
-    month: lastMonth,
-    day: 1,
-    hour: 0,
-    minute: 0,
-  })
-
-  yearDumpArray.push({
-    load: Math.round(yearlyLoad * 100) / 100,
-    year: lastYear,
-    month: 1,
-    day: 1,
-    hour: 0,
-    minute: 0,
+// Function to aggregate load data for a specific period
+function aggregateLoadData(data, period) {
+  const aggregatedData = {}
+  data.forEach(entry => {
+    let key
+    const entryCopy = { ...entry }
+    switch (period) {
+      case 'day':
+        key = `${entryCopy.year}-${entryCopy.month}-${entryCopy.day}`
+        entryCopy.hour = 0
+        entryCopy.minute = 0
+        break
+      case 'month':
+        key = `${entryCopy.year}-${entryCopy.month}`
+        entryCopy.day = 0
+        entryCopy.minute = 0
+        break
+      case 'year':
+        key = `${entryCopy.year}`
+        entryCopy.month = 0
+        entryCopy.day = 0
+        entryCopy.minute = 0
+        break
+      default:
+        break
+    }
+    if (!aggregatedData[key]) {
+      aggregatedData[key] = {
+        load: entryCopy.load,
+        year: entryCopy.year,
+        month: entryCopy.month,
+        day: entryCopy.day,
+        hour: entryCopy.hour,
+        minute: entryCopy.minute,
+      }
+    } else {
+      aggregatedData[key].load += entryCopy.load
+    }
   })
-
-  return {
-    dailyLoad: dayDumpArray,
-    monthlyLoad: monthDumpArray,
-    yearlyLoad: yearDumpArray,
-  }
+  return Object.values(aggregatedData)
 }
 
 // Create data folder
@@ -226,67 +109,53 @@ if (!fs.existsSync(dir)) {
   fs.mkdirSync(dir)
 }
 
-const startingdate = config.startingdate
+const startingDate = config.startingDate
 const endingDate = config.endingDate
-const halfHourStartingdate = config.halfHourStartingdate
+const halfHourStartingDate = config.halfHourStartingDate
 
-const Elec = generateHalfAnHourData(
-  halfHourStartingdate,
+const elecHalfHourData = generateHalfAnHourData(
+  halfHourStartingDate,
   endingDate.endOf('day'),
   0.12,
   0.36
 )
-const Gaz = generateData(startingdate, endingDate, 16, 68)
-const Eau = generateData(startingdate, endingDate, 200, 300)
-
-const dumpElec = {
-  'com.grandlyon.enedis.minute': Elec.halfAnHourLoad,
-  'com.grandlyon.enedis.day': Elec.dailyLoad,
-  'com.grandlyon.enedis.month': Elec.monthlyLoad,
-  'com.grandlyon.enedis.year': Elec.yearlyLoad,
-}
-const dumpStringElec = JSON.stringify(dumpElec)
-fs.writeFile('data/dayData-elec.json', dumpStringElec, function (err) {
-  if (err) console.log('error', err)
-})
+// Aggregate all half-hour data available and generate more daily data between startingDate and halfHourStartingDate
+const elecAggregatedDayData = aggregateLoadData(elecHalfHourData, 'day')
+const elecGeneratedDayData = generateData(
+  startingDate,
+  halfHourStartingDate.minus({ days: 1 }),
+  4,
+  8
+)
+const elecDayData = [...elecAggregatedDayData, ...elecGeneratedDayData]
+const elecMonthData = aggregateLoadData(elecDayData, 'month')
+const elecYearData = aggregateLoadData(elecMonthData, 'year')
 
-const dumpGas = {
-  'com.grandlyon.grdf.day': Gaz.dailyLoad,
-  'com.grandlyon.grdf.month': Gaz.monthlyLoad,
-  'com.grandlyon.grdf.year': Gaz.yearlyLoad,
-}
-const dumpStringGas = JSON.stringify(dumpGas)
-fs.writeFile('data/dayData-gas.json', dumpStringGas, function (err) {
-  if (err) console.log('error', err)
-})
+const gasDayData = generateData(startingDate, endingDate, 16, 68)
+const gasMonthData = aggregateLoadData(gasDayData, 'month')
+const gasYearData = aggregateLoadData(gasMonthData, 'year')
 
-const dumpWater = {
-  'com.grandlyon.egl.day': Eau.dailyLoad,
-  'com.grandlyon.egl.month': Eau.monthlyLoad,
-  'com.grandlyon.egl.year': Eau.yearlyLoad,
-}
-const dumpStringWater = JSON.stringify(dumpWater)
-fs.writeFile('data/dayData-water.json', dumpStringWater, function (err) {
-  if (err) console.log('error', err)
-})
+const waterDayData = generateData(startingDate, endingDate, 200, 300)
+const waterMonthData = aggregateLoadData(waterDayData, 'month')
+const waterYearData = aggregateLoadData(waterMonthData, 'year')
 
 const dump = {
-  'com.grandlyon.enedis.minute': Elec.halfAnHourLoad,
-  'com.grandlyon.enedis.day': Elec.dailyLoad,
-  'com.grandlyon.enedis.month': Elec.monthlyLoad,
-  'com.grandlyon.enedis.year': Elec.yearlyLoad,
-
-  'com.grandlyon.grdf.day': Gaz.dailyLoad,
-  'com.grandlyon.grdf.month': Gaz.monthlyLoad,
-  'com.grandlyon.grdf.year': Gaz.yearlyLoad,
-
-  'com.grandlyon.egl.day': Eau.dailyLoad,
-  'com.grandlyon.egl.month': Eau.monthlyLoad,
-  'com.grandlyon.egl.year': Eau.yearlyLoad,
+  'com.grandlyon.enedis.minute': elecHalfHourData,
+  'com.grandlyon.enedis.day': elecDayData,
+  'com.grandlyon.enedis.month': elecMonthData,
+  'com.grandlyon.enedis.year': elecYearData,
+
+  'com.grandlyon.grdf.day': gasDayData,
+  'com.grandlyon.grdf.month': gasMonthData,
+  'com.grandlyon.grdf.year': gasYearData,
+
+  'com.grandlyon.egl.day': waterDayData,
+  'com.grandlyon.egl.month': waterMonthData,
+  'com.grandlyon.egl.year': waterYearData,
 }
 
 const dumpString = JSON.stringify(dump)
 
-fs.writeFile('data/dayData.json', dumpString, function (err) {
+fs.writeFile('data/loads.json', dumpString, function (err) {
   if (err) console.log('error', err)
 })
diff --git a/scripts/importData.sh b/scripts/importData.sh
index 3c3f3fb341b55d4748d25e419056189d09f2bb60..feac52a29b5b4022fb0ee19fb5b0400cf17bb2fb 100755
--- a/scripts/importData.sh
+++ b/scripts/importData.sh
@@ -1,4 +1,14 @@
-cozyssid=""
-read -p "Please provide Cozyssid (can be found after connection in browser dev tool): " cozyssid
-ACH -t cozyssid -u http://cozy.tools:8080 import ./data/dayData.json
-rm cozyssid
\ No newline at end of file
+# Drop previous data
+ACH -u http://cozy.tools:8080 -y drop \
+  com.grandlyon.enedis.minute \
+  com.grandlyon.enedis.day \
+  com.grandlyon.enedis.month \
+  com.grandlyon.enedis.year \
+  com.grandlyon.grdf.day \
+  com.grandlyon.grdf.month \
+  com.grandlyon.grdf.year \
+  com.grandlyon.egl.day \
+  com.grandlyon.egl.month \
+  com.grandlyon.egl.year
+  # Import new data
+ACH -u http://cozy.tools:8080 import ./data/loads.json
\ No newline at end of file
diff --git a/src/assets/icons/ico/offPeakHour.svg b/src/assets/icons/ico/offPeakHour.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6eaab48884b42a653e2bf0ec5e3219ec1c53240b
--- /dev/null
+++ b/src/assets/icons/ico/offPeakHour.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="40" height="41" viewBox="0 0 40 41" fill="none">
+  <path
+    d="M33.4768 9.34225L28.9552 5.58922C28.3386 5.07612 27.4137 5.14942 26.8852 5.77981C26.3714 6.39554 26.4595 7.31913 27.0761 7.8469L31.583 11.5999C32.1996 12.113 33.1245 12.0397 33.653 11.4093C34.1815 10.7936 34.0934 9.87002 33.4768 9.34225ZM8.40231 11.5999L12.9093 7.8469C13.5405 7.31913 13.6286 6.39554 13.1001 5.77981C12.5863 5.14942 11.6614 5.07612 11.0448 5.58922L6.52319 9.34225C5.90661 9.87002 5.81852 10.7936 6.34702 11.4093C6.86085 12.0397 7.78572 12.113 8.40231 11.5999ZM20 7.86156C12.7037 7.86156 6.78744 13.7696 6.78744 21.0558C6.78744 28.3419 12.7037 34.25 20 34.25C27.2963 34.25 33.2126 28.3419 33.2126 21.0558C33.2126 13.7696 27.2963 7.86156 20 7.86156ZM20 31.318C14.3333 31.318 9.72357 26.7146 9.72357 21.0558C9.72357 15.3969 14.3333 10.7936 20 10.7936C25.6667 10.7936 30.2764 15.3969 30.2764 21.0558C30.2764 26.7146 25.6667 31.318 20 31.318Z"
+    fill="#D87B39" />
+  <path
+    d="M20 14.25C18.8063 14.25 17.6325 14.5552 16.59 15.1367C15.5476 15.7182 14.6711 16.5566 14.044 17.5723C13.4168 18.5879 13.0598 19.7471 13.0069 20.9396C12.954 22.1321 13.2068 23.3183 13.7415 24.3855C14.2762 25.4528 15.0749 26.3655 16.0618 27.0371C17.0486 27.7087 18.1908 28.1167 19.3798 28.2225C20.5688 28.3282 21.765 28.1282 22.8549 27.6414C23.9448 27.1545 24.8921 26.3971 25.6068 25.441L20 21.25V14.25Z"
+    fill="white" />
+  <path
+    d="M21.009 21.5821L25.5227 24.3753C25.9915 24.6546 26.1388 25.2831 25.8575 25.7719C25.5897 26.2467 25.0003 26.3864 24.545 26.1071L19.6563 23.0485C19.2411 22.7971 19 22.3363 19 21.8475V15.2555C19 14.6969 19.4286 14.25 19.9643 14.25H20.0447C20.5804 14.25 21.009 14.6969 21.009 15.2555V21.5821Z"
+    fill="#D87B39" />
+</svg>
\ No newline at end of file
diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx
index f62d1ff5506e91ef57d8d556b8d805d03f32eaaf..079b98bd5a78e418233b508098bda144c53b7faf 100644
--- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx
+++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.spec.tsx
@@ -32,6 +32,7 @@ jest.mock('services/enedisMonthlyAnalysisData.service', () => {
   return jest.fn(() => ({
     getEnedisMonthlyAnalysisByDate: mockGetEnedisMonthlyAnalysisByDate,
     aggregateValuesToDataLoad: mockAggregateValuesToDataLoad,
+    getOffPeakHours: () => null,
   }))
 })
 const mockPerfIndicator: PerformanceIndicator = {
@@ -52,10 +53,10 @@ describe('ElecHalfHourMonthlyAnalysis component', () => {
   beforeEach(() => {
     jest.clearAllMocks()
   })
-
   it('should be rendered correctly when isHalfHourActivated is false', async () => {
     mockCheckDoctypeEntries.mockResolvedValueOnce(false)
     mockGetPrices.mockResolvedValue(allLastFluidPrices[0])
+
     const { container } = render(
       <Provider store={store}>
         <ElecHalfHourMonthlyAnalysis perfIndicator={mockPerfIndicator} />
diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx
index 4656b493d2289f96eab56868efd15bb8da8908b1..332d2177ccc1fb7d3231789e62c24fcdb6be3e43 100644
--- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx
+++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecHalfHourMonthlyAnalysis.tsx
@@ -3,6 +3,7 @@ import IconButton from '@material-ui/core/IconButton'
 import LeftArrowIcon from 'assets/icons/ico/left-arrow.svg'
 import MaxPowerIcon from 'assets/icons/ico/maxPower.svg'
 import MinIcon from 'assets/icons/ico/minimum.svg'
+import OffPeakHourIcon from 'assets/icons/ico/offPeakHour.svg'
 import RightArrowIcon from 'assets/icons/ico/right-arrow.svg'
 import Loader from 'components/Loader/Loader'
 import { useClient } from 'cozy-client'
@@ -21,6 +22,7 @@ import EnedisMonthlyAnalysisDataService from 'services/enedisMonthlyAnalysisData
 import FluidPricesService from 'services/fluidsPrices.service'
 import { useAppSelector } from 'store/hooks'
 import { getNavPicto } from 'utils/picto'
+import { OffPeakHours } from 'utils/utils'
 import ElecHalfHourChart from './ElecHalfHourChart'
 import ElecInfoModal from './ElecInfoModal'
 import './elecHalfHourMonthlyAnalysis.scss'
@@ -43,6 +45,7 @@ const ElecHalfHourMonthlyAnalysis = ({
   const [facturePercentage, setFacturePercentage] = useState<number>()
   const [elecPrice, setElecPrice] = useState<FluidPrice>()
   const [openInfoModal, setOpenInfoModal] = useState<boolean>(false)
+  const [offPeakHours, setOffPeakHours] = useState<OffPeakHours[]>()
 
   const handleChangeWeek = useCallback(() => {
     setIsWeekend(prev => !prev)
@@ -132,6 +135,26 @@ const ElecHalfHourMonthlyAnalysis = ({
     }
   }, [analysisMonth, client])
 
+  useEffect(() => {
+    let subscribed = true
+    const enedisMonthlyAnalysisDataService =
+      new EnedisMonthlyAnalysisDataService(client)
+    async function getOffPeakHours() {
+      if (subscribed) {
+        const offPeakHours =
+          await enedisMonthlyAnalysisDataService.getOffPeakHours()
+        if (offPeakHours) {
+          setOffPeakHours(offPeakHours)
+        }
+      }
+    }
+    getOffPeakHours()
+
+    return () => {
+      subscribed = false
+    }
+  }, [client])
+
   return (
     <div className="special-elec-container">
       <Icon
@@ -199,6 +222,28 @@ const ElecHalfHourMonthlyAnalysis = ({
                       )}
                     </div>
                   </div>
+                  {enedisAnalysisValues?.offPeakHoursRatio != null && (
+                    <div className="container">
+                      <Icon
+                        icon={OffPeakHourIcon}
+                        size={40}
+                        className="minIcon"
+                      />
+                      <div className="text">
+                        <div className="min text-18-normal">
+                          {t('special_elec.offPeakHour')}
+                        </div>
+                      </div>
+                      <div className="value kvAval">
+                        <div className="text-18-bold">
+                          {Math.round(
+                            enedisAnalysisValues.offPeakHoursRatio * 100
+                          )}
+                          <span className="text-18-normal"> %</span>
+                        </div>
+                      </div>
+                    </div>
+                  )}
                   <div className="container consomin">
                     <Icon icon={MinIcon} size={40} className="minIcon" />
                     <div className="text text-18-normal">
@@ -243,7 +288,11 @@ const ElecHalfHourMonthlyAnalysis = ({
           )}
         </>
       )}
-      <ElecInfoModal open={openInfoModal} handleCloseClick={toggleOpenModal} />
+      <ElecInfoModal
+        open={openInfoModal}
+        offPeakHours={offPeakHours}
+        handleCloseClick={toggleOpenModal}
+      />
     </div>
   )
 }
diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.spec.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.spec.tsx
index f41b110bfd9928fd4e04cdd8b6d7a72aceec2cf1..e00eecbcf5d8176eee2aef7ba0d31f5355714503 100644
--- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.spec.tsx
+++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.spec.tsx
@@ -3,9 +3,28 @@ import React from 'react'
 import ElecInfoModal from './ElecInfoModal'
 
 describe('ElecInfoModal component', () => {
-  it('should be rendered correctly', () => {
+  it('should be rendered correctly without off-peak hour info', () => {
     const { baseElement } = render(
-      <ElecInfoModal open={true} handleCloseClick={jest.fn()} />
+      <ElecInfoModal
+        open={true}
+        offPeakHours={undefined}
+        handleCloseClick={jest.fn()}
+      />
+    )
+    expect(baseElement).toMatchSnapshot()
+  })
+  it('should be rendered correctly with off-peak hour info', () => {
+    const { baseElement } = render(
+      <ElecInfoModal
+        open={true}
+        offPeakHours={[
+          {
+            start: { hour: 10, minute: 0 },
+            end: { hour: 18, minute: 0 },
+          },
+        ]}
+        handleCloseClick={jest.fn()}
+      />
     )
     expect(baseElement).toMatchSnapshot()
   })
diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx
index 8f61f8aff2116494afb98998e1b2158a5c9f975d..d90558d7466add54019bd1fb73b204842fbffc81 100644
--- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx
+++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/ElecInfoModal.tsx
@@ -4,16 +4,30 @@ 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 {
+  OffPeakHours,
+  formatListWithAnd,
+  formatOffPeakHours,
+} from 'utils/utils'
 import './elecInfoModal.scss'
 
 interface ElecInfoModalProps {
   open: boolean
+  offPeakHours: OffPeakHours[] | undefined
   handleCloseClick: () => void
 }
 
-const ElecInfoModal = ({ open, handleCloseClick }: ElecInfoModalProps) => {
+const ElecInfoModal = ({
+  open,
+  offPeakHours,
+  handleCloseClick,
+}: ElecInfoModalProps) => {
   const { t } = useI18n()
-
+  const displayedOffPeakHours =
+    offPeakHours &&
+    formatListWithAnd(
+      offPeakHours.map(offPeakHour => formatOffPeakHours(offPeakHour))
+    )
   return (
     <Dialog
       open={open}
@@ -37,18 +51,30 @@ const ElecInfoModal = ({ open, handleCloseClick }: ElecInfoModalProps) => {
       <div className="elecInfoModal">
         <div className="title text-18-bold">{t('elec_info_modal.title1')}</div>
         <div className="text">
-          {t('elec_info_modal.text1')}
+          {t('elec_info_modal.text1-1')}
           <br />
-          {t('elec_info_modal.text2')}
+          {t('elec_info_modal.text1-2')}
         </div>
         <div className="title text-18-bold">{t('elec_info_modal.title2')}</div>
         <div className="text">
-          {t('elec_info_modal.text3')}
+          {t('elec_info_modal.text2-1')}
           <br />
-          {t('elec_info_modal.text4')}
+          {t('elec_info_modal.text2-2')}
           <br />
-          {t('elec_info_modal.text5')}
+          {t('elec_info_modal.text2-3')}
         </div>
+        {offPeakHours && (
+          <>
+            <div className="title text-18-bold">
+              {t('elec_info_modal.title3')}
+            </div>
+            <div className="text">
+              {t('elec_info_modal.text3-1', {
+                offPeakHours: displayedOffPeakHours,
+              })}
+            </div>
+          </>
+        )}
       </div>
     </Dialog>
   )
diff --git a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap
index 55b41b0e8aa630ef74c40b99539bdeea6581b22d..120a26734e421ffd093d96aa49886940c689423f 100644
--- a/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap
+++ b/src/components/Analysis/ElecHalfHourMonthlyAnalysis/__snapshots__/ElecInfoModal.spec.tsx.snap
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`ElecInfoModal component should be rendered correctly 1`] = `
+exports[`ElecInfoModal component should be rendered correctly with off-peak hour info 1`] = `
 <body
   style="padding-right: 0px; overflow: hidden;"
 >
@@ -71,9 +71,9 @@ exports[`ElecInfoModal component should be rendered correctly 1`] = `
           <div
             class="text"
           >
-            elec_info_modal.text1
+            elec_info_modal.text1-1
             <br />
-            elec_info_modal.text2
+            elec_info_modal.text1-2
           </div>
           <div
             class="title text-18-bold"
@@ -83,11 +83,121 @@ exports[`ElecInfoModal component should be rendered correctly 1`] = `
           <div
             class="text"
           >
-            elec_info_modal.text3
+            elec_info_modal.text2-1
             <br />
-            elec_info_modal.text4
+            elec_info_modal.text2-2
             <br />
-            elec_info_modal.text5
+            elec_info_modal.text2-3
+          </div>
+          <div
+            class="title text-18-bold"
+          >
+            elec_info_modal.title3
+          </div>
+          <div
+            class="text"
+          >
+            elec_info_modal.text3-1
+          </div>
+        </div>
+      </div>
+    </div>
+    <div
+      data-test="sentinelEnd"
+      tabindex="0"
+    />
+  </div>
+</body>
+`;
+
+exports[`ElecInfoModal component should be rendered correctly without off-peak hour info 1`] = `
+<body
+  style="padding-right: 0px; overflow: hidden;"
+>
+  <div
+    aria-hidden="true"
+  />
+  <div
+    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
+        aria-labelledby="accessibility-title"
+        class="MuiPaper-root MuiDialog-paper modal-paper MuiDialog-paperScrollPaper MuiDialog-paperWidthSm MuiPaper-elevation24 MuiPaper-rounded"
+        role="dialog"
+      >
+        <div
+          id="accessibility-title"
+        >
+          elec_info_modal.accessibility.window_title
+        </div>
+        <button
+          aria-label="elec_info_modal.accessibility.button_close"
+          class="MuiButtonBase-root MuiIconButton-root modal-paper-close-button"
+          tabindex="0"
+          type="button"
+        >
+          <span
+            class="MuiIconButton-label"
+          >
+            <svg
+              class="styles__icon___23x3R"
+              height="16"
+              width="16"
+            >
+              <use
+                xlink:href="#test-file-stub"
+              />
+            </svg>
+          </span>
+          <span
+            class="MuiTouchRipple-root"
+          />
+        </button>
+        <div
+          class="elecInfoModal"
+        >
+          <div
+            class="title text-18-bold"
+          >
+            elec_info_modal.title1
+          </div>
+          <div
+            class="text"
+          >
+            elec_info_modal.text1-1
+            <br />
+            elec_info_modal.text1-2
+          </div>
+          <div
+            class="title text-18-bold"
+          >
+            elec_info_modal.title2
+          </div>
+          <div
+            class="text"
+          >
+            elec_info_modal.text2-1
+            <br />
+            elec_info_modal.text2-2
+            <br />
+            elec_info_modal.text2-3
           </div>
         </div>
       </div>
diff --git a/src/components/Options/ExportData/Modals/exportLoadingModal.tsx b/src/components/Options/ExportData/Modals/exportLoadingModal.tsx
index 35772077fc0e3b9c28fa0aed3dcff189fb839bc6..f6058bed9c22a9a093ed02e7d276bbccecadb78e 100644
--- a/src/components/Options/ExportData/Modals/exportLoadingModal.tsx
+++ b/src/components/Options/ExportData/Modals/exportLoadingModal.tsx
@@ -12,6 +12,7 @@ import { Datachart, Dataload, TimePeriod } from 'models'
 import React, { useCallback, useEffect } from 'react'
 import ConsumptionDataManager from 'services/consumption.service'
 import EnedisMonthlyAnalysisDataService from 'services/enedisMonthlyAnalysisData.service'
+import { formatTwoDigits } from 'utils/utils'
 import * as XLSX from 'xlsx'
 import './exportLoadingModal.scss'
 
@@ -64,9 +65,7 @@ const ExportLoadingModal = ({
       fluidType: FluidType
     ): Promise<ExportDataRow> => {
       const dataRow: ExportDataRow = {}
-      dataRow[t('export.month')] = dataload.date.month
-        .toString()
-        .padStart(2, '0')
+      dataRow[t('export.month')] = formatTwoDigits(dataload.date.month)
       dataRow[t('export.year')] = dataload.date.year
       dataRow[
         `${t('export.consumption')} (${t(
diff --git a/src/components/ProfileType/ProfileTypeFormDateSelection/ProfileTypeFormDateSelection.tsx b/src/components/ProfileType/ProfileTypeFormDateSelection/ProfileTypeFormDateSelection.tsx
index 42f247a432bcf8c1d8795cb3002b158919b76817..c1e63fc33d4b952ce125fdb309376e1066800503 100644
--- a/src/components/ProfileType/ProfileTypeFormDateSelection/ProfileTypeFormDateSelection.tsx
+++ b/src/components/ProfileType/ProfileTypeFormDateSelection/ProfileTypeFormDateSelection.tsx
@@ -6,7 +6,7 @@ import { ProfileTypeStepForm } from 'enums'
 import { DateTime } from 'luxon'
 import { ProfileType, ProfileTypeAnswer, ProfileTypeValues } from 'models'
 import React, { useCallback, useState } from 'react'
-import { getMonthFullName } from 'utils/utils'
+import { formatTwoDigits, getMonthFullName } from 'utils/utils'
 
 interface ProfileTypeFormDateSelectionProps {
   step: ProfileTypeStepForm
@@ -32,7 +32,7 @@ const ProfileTypeFormDateSelection = ({
   const [selectedYear, setSelectedYear] = useState<number>(DateTime.now().year)
   const [selectedMonth, setSelectedMonth] = useState<SelectionMonth>({
     label: DateTime.now().toLocaleString({ month: 'long' }),
-    value: DateTime.now().month.toString().padStart(2, '0'), // Date.getMonth starts at 0
+    value: formatTwoDigits(DateTime.now().month), // Date.getMonth starts at 0
   })
   const buildISODate = (year: string, month: string) =>
     DateTime.fromISO(`${year}-${month}-01`)
diff --git a/src/locales/fr.json b/src/locales/fr.json
index 3dd9b0056a1ba38cd05c7c162e477ce9c12f00f3..56927c9737dca41569517e13a96f81834891f65e 100644
--- a/src/locales/fr.json
+++ b/src/locales/fr.json
@@ -132,6 +132,7 @@
     "percentage": "Part dans la facture",
     "price": "Soit",
     "maxPower": "Puissance maximum atteinte",
+    "offPeakHour": "Consommation en heures creuses",
     "showModal": "Plus d'infos"
   },
   "elec_info_modal": {
@@ -140,12 +141,14 @@
       "button_close": "close-modal"
     },
     "title1": "Qu’est-ce que la consommation minimum\u00a0?",
+    "text1-1": "Elle correspond à votre plus petite consommation du mois sur un créneau d'une demi-heure.",
+    "text1-2": "Nous extrapolons cette consommation sur 1 mois afin vous donner un aperçu de la consommation de vos consommations électriques en veille (box, télé, chargeurs, ...) ou encore de celle, incompressible, de vos appareils de froid (frigo, congélateur).",
     "title2": "Qu’est-ce que la puissance maximum\u00a0?",
-    "text1": "Elle correspond à votre plus petite consommation du mois sur un créneau d'une demi-heure.",
-    "text2": "Nous extrapolons cette consommation sur 1 mois afin vous donner un aperçu de la consommation de vos consommations électriques en veille (box, télé, chargeurs, ...) ou encore de celle, incompressible, de vos appareils de froid (frigo, congélateur).",
-    "text3": "C’est la puissance maximum délivrée par tous les appareils fonctionnant au même moment dans votre logement.",
-    "text4": "Vous avez choisi une puissance maximum dans votre offre d’électricité (3, 6 ou 9 kVA...) que vous ne devez pas dépasser pour ne pas faire sauter votre compteur. ",
-    "text5": "Cette puissance varie d'un mois à l'autre, regardez cette valeur sur l'ensemble de l'année pour vérifier si votre puissance souscrite correspond bien à votre usage."
+    "text2-1": "C’est la puissance maximum délivrée par tous les appareils fonctionnant au même moment dans votre logement.",
+    "text2-2": "Vous avez choisi une puissance maximum dans votre offre d’électricité (3, 6 ou 9 kVA...) que vous ne devez pas dépasser pour ne pas faire sauter votre compteur. ",
+    "text2-3": "Cette puissance varie d'un mois à l'autre, regardez cette valeur sur l'ensemble de l'année pour vérifier si votre puissance souscrite correspond bien à votre usage.",
+    "title3": "Sur quelle base est calculé mon ratio Heures Pleines / Heures Creuses\u00a0?",
+    "text3-1": "D'après Enedis, vos plages d'heures creuses sont les suivantes : %{offPeakHours}. Nous avons donc simplement calculé, à l'aide vos consommations à la demi-heure, quelle part de votre consommation est réalisée sur vos heures creuses."
   },
   "auth": {
     "enedissgegrandlyon": {
diff --git a/src/models/account.model.ts b/src/models/account.model.ts
index 87b5d1ee8aed46020552ccfd97c48c2207f8517d..329d7a37703d855da6c15ecc76bfff6a508020ba 100644
--- a/src/models/account.model.ts
+++ b/src/models/account.model.ts
@@ -6,9 +6,10 @@ export interface Account extends AccountAttributes {
   _type?: string
   cozyMetadata?: Record<string, any>
 }
-export interface SgeConsentData {
+export interface SgeAccountData {
   consentId: number
   expirationDate: string
+  offPeakHours?: string
 }
 
 export interface AccountAttributes {
@@ -18,7 +19,7 @@ export interface AccountAttributes {
   identifier?: string
   state?: string | null
   name?: string
-  data?: SgeConsentData
+  data?: SgeAccountData
   oauth_callback_results?: Record<string, any>
 }
 
diff --git a/src/models/enedisMonthlyAnalysis.ts b/src/models/enedisMonthlyAnalysis.ts
index 7c0547ce604b7d60bd2963b79773ed34d3906eb0..b8d00dfb7449172178746399b9bc6fffd19fca36 100644
--- a/src/models/enedisMonthlyAnalysis.ts
+++ b/src/models/enedisMonthlyAnalysis.ts
@@ -7,6 +7,7 @@ export interface EnedisMonthlyAnalysisData {
   year: number
   minimumLoad: number | null
   maxPower: number | null
+  offPeakHoursRatio: number | null
 }
 export interface AggregatedEnedisMonthlyDataloads {
   week: Dataload[]
diff --git a/src/services/enedisMonthlyAnalysisData.service.spec.ts b/src/services/enedisMonthlyAnalysisData.service.spec.ts
index fd72985c39bc3781c9645494dfe27fb7d44840d8..5cad8bbbde48da15bdb1cf13135fc71817780c7c 100644
--- a/src/services/enedisMonthlyAnalysisData.service.spec.ts
+++ b/src/services/enedisMonthlyAnalysisData.service.spec.ts
@@ -13,17 +13,6 @@ import EnedisMonthlyAnalysisDataService from './enedisMonthlyAnalysisData.servic
 describe('Enedis Monthly Analysis service', () => {
   const emas = new EnedisMonthlyAnalysisDataService(mockClient)
 
-  it('should return all Enedis Monthly Analysis', async () => {
-    const mockQueryResult: QueryResult<EnedisMonthlyAnalysisData[]> = {
-      data: mockEnedisMonthlyAnalysisArray,
-      bookmark: '',
-      next: false,
-      skip: 0,
-    }
-    mockClient.query.mockResolvedValueOnce(mockQueryResult)
-    const result = await emas.getAllEnedisMonthlyAnalysisData()
-    expect(result).toEqual(mockEnedisMonthlyAnalysisArray)
-  })
   it('should return no enedis analysis', async () => {
     const mockQueryResult: QueryResult<EnedisMonthlyAnalysisData[]> = {
       data: [],
diff --git a/src/services/enedisMonthlyAnalysisData.service.ts b/src/services/enedisMonthlyAnalysisData.service.ts
index dcbcb3a2bc2a19b96cc38fd3cf44cad6ead1d09f..ef34a3e33c3aba8fdebc6a301bd6104864f6b34b 100644
--- a/src/services/enedisMonthlyAnalysisData.service.ts
+++ b/src/services/enedisMonthlyAnalysisData.service.ts
@@ -3,16 +3,27 @@ import { Client, Q, QueryDefinition, QueryResult } from 'cozy-client'
 import logger from 'cozy-logger'
 import {
   ENEDIS_MAXPOWER_DOCTYPE,
+  ENEDIS_MINUTE_DOCTYPE,
   ENEDIS_MONTHLY_ANALYSIS_DATA_DOCTYPE,
+  ENEDIS_MONTH_DOCTYPE,
 } from 'doctypes'
-import { DataloadState } from 'enums'
+import { DataloadState, FluidType } from 'enums'
 import { DateTime } from 'luxon'
-import { Dataload, MaxPowerEntity } from 'models'
+import { Dataload, DataloadEntity, MaxPowerEntity } from 'models'
 import {
   AggregatedEnedisMonthlyDataloads,
   EnedisMonthlyAnalysisData,
 } from 'models/enedisMonthlyAnalysis'
 import logApp from 'utils/logger'
+import {
+  OffPeakHours,
+  formatOffPeakHours,
+  parseOffPeakHours,
+  roundOffPeakHours,
+  splitOffPeakHours,
+} from 'utils/utils'
+import AccountService from './account.service'
+import ConfigService from './fluidConfig.service'
 
 const logStack = logger.namespace('enedisMonthlyAnalysisDataService')
 export default class EnedisMonthlyAnalysisDataService {
@@ -22,20 +33,6 @@ export default class EnedisMonthlyAnalysisDataService {
     this._client = _client
   }
 
-  /**
-   * Retrieve all exploration entities from db
-   */
-  public async getAllEnedisMonthlyAnalysisData(): Promise<
-    EnedisMonthlyAnalysisData[]
-  > {
-    const query: QueryDefinition = Q(ENEDIS_MONTHLY_ANALYSIS_DATA_DOCTYPE)
-    const {
-      data: enedisMonthlyAnalysisData,
-    }: QueryResult<EnedisMonthlyAnalysisData[]> =
-      await this._client.query(query)
-    return enedisMonthlyAnalysisData
-  }
-
   /**
    * getLastEnedisMonthlyAnalysis
    */
@@ -154,4 +151,107 @@ export default class EnedisMonthlyAnalysisDataService {
 
     return data.data
   }
+
+  public async getOffPeakHours(): Promise<OffPeakHours[] | null> {
+    const accountService = new AccountService(this._client)
+    const fluidConfig = new ConfigService().getFluidConfig()
+    const account = await accountService.getAccountByType(
+      fluidConfig[FluidType.ELECTRICITY].konnectorConfig.slug
+    )
+    if (!account?.data?.offPeakHours) return null
+    return parseOffPeakHours(account.data.offPeakHours)
+  }
+
+  public async getOffPeakHoursRatio(
+    year: number,
+    month: number,
+    offPeakHours: OffPeakHours[]
+  ): Promise<number> {
+    const roundedOffPeakHours = roundOffPeakHours(offPeakHours)
+    const splittedOffPeakHours = splitOffPeakHours(roundedOffPeakHours)
+    const formattedOffPeakHours = splittedOffPeakHours
+      .map(split => formatOffPeakHours(split))
+      .join(';')
+    logStack(
+      'debug',
+      `Fetching half-hour consumption for the ranges : ${formattedOffPeakHours}`
+    )
+
+    const offPeakHoursConsumption = await this.getOffPeakHourConsumption(
+      splittedOffPeakHours,
+      year,
+      month
+    )
+
+    const monthQuery: QueryDefinition = Q(ENEDIS_MONTH_DOCTYPE)
+      .where({ year: year, month: month })
+      .indexFields(['year', 'month'])
+      .limitBy(1)
+    const { data: monthDocs } = await this._client.query(monthQuery)
+    return offPeakHoursConsumption / monthDocs[0].load
+  }
+
+  private async getOffPeakHourConsumption(
+    offPeakHours: OffPeakHours[],
+    year: number,
+    month: number
+  ) {
+    let minuteDocs: DataloadEntity[] = []
+
+    for (const range of offPeakHours) {
+      const { start, end } = range
+      const minuteQuery: QueryDefinition = Q(ENEDIS_MINUTE_DOCTYPE)
+        .where({
+          year: year,
+          month: month,
+          $and: [
+            {
+              $or: [
+                { hour: { $gt: start.hour } },
+                {
+                  $and: [
+                    { hour: start.hour },
+                    { minute: { $gt: start.minute } },
+                  ],
+                },
+              ],
+            },
+            {
+              $or: [
+                { hour: { $lt: end.hour } },
+                {
+                  $and: [{ hour: end.hour }, { minute: { $lte: end.minute } }],
+                },
+              ],
+            },
+          ],
+        })
+        .limitBy(500)
+      const { data: docs }: QueryResult<DataloadEntity[]> =
+        await this._client.query(minuteQuery)
+
+      minuteDocs = [...minuteDocs, ...docs]
+
+      // if the half-hour 23H30-0H00 is required, add the 0H00-0H30 consumption because the data is shifted in DB
+      if (end.hour === 23 && end.minute === 59) {
+        const firstHalfHourQuery: QueryDefinition = Q(
+          ENEDIS_MINUTE_DOCTYPE
+        ).where({
+          year: year,
+          month: month,
+          hour: 0,
+          minute: 0,
+        })
+        const { data: docs }: QueryResult<DataloadEntity[]> =
+          await this._client.query(firstHalfHourQuery)
+
+        minuteDocs = [...minuteDocs, ...docs]
+      }
+    }
+    logStack('debug', `Found ${minuteDocs.length} documents`)
+    const offPeakHoursConsumption = minuteDocs.reduce((sum: number, doc) => {
+      return sum + doc.load
+    }, 0)
+    return offPeakHoursConsumption
+  }
 }
diff --git a/src/targets/services/enedisHalfHourMonthlyAnalysis.ts b/src/targets/services/enedisHalfHourMonthlyAnalysis.ts
index 28c355598e3583ffda5b4d9a4952763d6be3365f..84b034a1e87c1b557436d2f4195e8e1a6aeb39a8 100644
--- a/src/targets/services/enedisHalfHourMonthlyAnalysis.ts
+++ b/src/targets/services/enedisHalfHourMonthlyAnalysis.ts
@@ -65,15 +65,21 @@ const populateArrayWithTotalData = (
     })
   }
 }
+
 /** Gets max Power value for a given month */
 const getMonthMaxPower = async (
   month: number,
   year: number,
   client: Client
 ) => {
-  const emas = new EnedisMonthlyAnalysisDataService(client)
+  const enedisMonthlyAnalysisDataService = new EnedisMonthlyAnalysisDataService(
+    client
+  )
   logStack('info', `Fetching max power for month ${month} of year ${year}`)
-  const data = await emas.getMaxPowerByDate(year, month)
+  const data = await enedisMonthlyAnalysisDataService.getMaxPowerByDate(
+    year,
+    month
+  )
   const maxPowerArray: number[] = []
   if (data?.length) {
     for (const day of data) {
@@ -82,6 +88,34 @@ const getMonthMaxPower = async (
   }
   return Math.max(...maxPowerArray)
 }
+
+/** Compute the off-peak hours consumption ratio */
+const getOffPeakHoursRatio = async (
+  month: number,
+  year: number,
+  client: Client
+) => {
+  logStack(
+    'info',
+    `Fetching off-peak hours ratio for month ${month} of year ${year}`
+  )
+  const enedisMonthlyAnalysisDataService = new EnedisMonthlyAnalysisDataService(
+    client
+  )
+  const offPeakHours = await enedisMonthlyAnalysisDataService.getOffPeakHours()
+  if (!offPeakHours) return null
+
+  const offPeakHoursRatio =
+    await enedisMonthlyAnalysisDataService.getOffPeakHoursRatio(
+      year,
+      month,
+      offPeakHours
+    )
+  logStack('info', `Off-peak hours ratio is ${offPeakHoursRatio} `)
+
+  return offPeakHoursRatio
+}
+
 /**
  * Get the average arrays of half-hour value on a given month
  */
@@ -110,6 +144,7 @@ const getEnedisMonthAnalysisData = async (
     weekEndDaysHalfHourAverageValues: [],
     minimumLoad: null,
     maxPower: null,
+    offPeakHoursRatio: null,
     month: month,
     year: year,
   }
@@ -135,14 +170,14 @@ const getEnedisMonthAnalysisData = async (
         populateArrayWithTotalData(weekValuesArray, halfHourDayData, false)
       }
     }
-    const numberofDaysInMonth = DateTime.fromObject({
+    const numberOfDaysInMonth = DateTime.fromObject({
       month: month,
       year: year,
     }).daysInMonth
     monthlyAveragesLoads.minimumLoad = getMinMonthlyLoad(
       weekEndValuesArray,
       weekValuesArray,
-      numberofDaysInMonth
+      numberOfDaysInMonth
     )
     const arrAvg = (arr: number[]) =>
       arr.reduce((a, b) => a + b, 0) / arr.length
@@ -158,6 +193,11 @@ const getEnedisMonthAnalysisData = async (
     monthlyAveragesLoads.weekDaysHalfHourAverageValues = weekAverages
     monthlyAveragesLoads.weekEndDaysHalfHourAverageValues = weekEndAverages
     monthlyAveragesLoads.maxPower = await getMonthMaxPower(month, year, client)
+    monthlyAveragesLoads.offPeakHoursRatio = await getOffPeakHoursRatio(
+      month,
+      year,
+      client
+    )
     return monthlyAveragesLoads
   }
 }
@@ -169,116 +209,127 @@ const getEnedisMonthAnalysisData = async (
 const syncEnedisMonthlyAnalysisDataDoctype = async ({
   client,
 }: EnedisMonthlyProps) => {
-  // SERVICE RUNS ONLY IF ENEDIS MINUTE IS ACTIVATED
-  const emas = new EnedisMonthlyAnalysisDataService(client)
-  const cs = new ConsumptionService(client)
-  const firstMinuteData = (await cs.getFirsDataDateFromDoctype(
+  const enedisMonthlyAnalysisDataService = new EnedisMonthlyAnalysisDataService(
+    client
+  )
+  const consumptionService = new ConsumptionService(client)
+  const firstMinuteData = (await consumptionService.getFirsDataDateFromDoctype(
     ENEDIS_MINUTE_DOCTYPE
   )) as DataloadEntity[]
 
-  const lastEnedisMonthlyAnalysis = await emas.getLastEnedisMonthlyAnalysis()
-  if (firstMinuteData?.[0]) {
-    // First creates the analysis of the month - 1
-    logStack('info', 'Fetching last Enedis monthly Analysis...')
-    const firstMinuteDate = DateTime.fromObject({
-      year: firstMinuteData[0].year,
-      month: firstMinuteData[0].month,
-    }).startOf('month')
-    const today = DateTime.local().setZone('utc', {
-      keepLocalTime: true,
-    })
-    const analysisDate: DateTime = today.minus({
-      month: 1,
-    })
-
-    const data = await getEnedisMonthAnalysisData(
-      client,
-      analysisDate.month,
-      analysisDate.year
+  const lastEnedisMonthlyAnalysis =
+    await enedisMonthlyAnalysisDataService.getLastEnedisMonthlyAnalysis()
+  // SERVICE RUNS ONLY IF ENEDIS MINUTE IS ACTIVATED
+  if (!firstMinuteData?.[0]) {
+    logStack(
+      'info',
+      'Enedis Minute is not activated or there is no data yet in this doctype'
     )
-    if (data) {
-      const created = await emas.createEnedisMonthlyAnalysisData(data)
-      if (created) {
-        logStack('success', 'Created successfully ! ')
-      } else {
-        logStack('error', 'Failed to create last Enedis monthly Analysis')
-        Sentry.captureException(
-          JSON.stringify({
-            error: 'Failed to create last Enedis monthly Analysis ',
-          })
-        )
-      }
+    return
+  }
+  // First creates the analysis of the month - 1
+  logStack('info', 'Fetching last Enedis monthly Analysis...')
+  const firstMinuteDate = DateTime.fromObject({
+    year: firstMinuteData[0].year,
+    month: firstMinuteData[0].month,
+  }).startOf('month')
+  const today = DateTime.local().setZone('utc', {
+    keepLocalTime: true,
+  })
+  const analysisDate: DateTime = today.minus({
+    month: 1,
+  })
+
+  const data = await getEnedisMonthAnalysisData(
+    client,
+    analysisDate.month,
+    analysisDate.year
+  )
+  if (data) {
+    const created =
+      await enedisMonthlyAnalysisDataService.createEnedisMonthlyAnalysisData(
+        data
+      )
+    if (created) {
+      logStack('success', 'Created successfully ! ')
+    } else {
+      logStack('error', 'Failed to create last Enedis monthly Analysis')
+      Sentry.captureException(
+        JSON.stringify({
+          error: 'Failed to create last Enedis monthly Analysis ',
+        })
+      )
     }
-    logStack('info', 'Getting first enedis half hour data date')
+  }
+  logStack('info', 'Getting first enedis half hour data date')
 
-    if (lastEnedisMonthlyAnalysis.length > 0) {
-      // If user has more than one entry (already synced), fetch the full history
-      const firstEnedisMonthlyAnalysis = (await cs.getFirsDataDateFromDoctype(
+  if (lastEnedisMonthlyAnalysis.length > 0) {
+    // If user has more than one entry (already synced), fetch the full history
+    const firstEnedisMonthlyAnalysis =
+      (await consumptionService.getFirsDataDateFromDoctype(
         ENEDIS_MONTHLY_ANALYSIS_DATA_DOCTYPE
       )) as EnedisMonthlyAnalysisData[]
-      if (
-        firstEnedisMonthlyAnalysis[0]?.month === firstMinuteData[0].month &&
-        firstEnedisMonthlyAnalysis[0]?.year === firstMinuteData[0].year
-      ) {
-        logStack('info', 'Every Enedis Analysis already synchronized')
-        return
-      } else if (firstEnedisMonthlyAnalysis) {
-        logStack(
-          'info',
-          'Doctype is partially completed, fetching all available history'
-        )
-        const firstEnedisMonthlyAnalysisDate = DateTime.fromObject({
-          year: firstEnedisMonthlyAnalysis[0].year,
-          month: firstEnedisMonthlyAnalysis[0].month,
-        }).startOf('month')
-        const diffInmonths = Math.ceil(
-          firstEnedisMonthlyAnalysisDate.diff(firstMinuteDate, 'months').months
-        )
-        for (let i = 1; i < diffInmonths; i++) {
-          const analysistoCreate = firstEnedisMonthlyAnalysisDate.minus({
-            month: i,
-          })
-          const data = await getEnedisMonthAnalysisData(
-            client,
-            analysistoCreate.month,
-            analysistoCreate.year
-          )
-          if (data) {
-            await emas.createEnedisMonthlyAnalysisData(data)
-          }
-        }
-      }
-    } else {
-      // If user only have the last analysis available, fetch one year history
+    if (
+      firstEnedisMonthlyAnalysis[0]?.month === firstMinuteData[0].month &&
+      firstEnedisMonthlyAnalysis[0]?.year === firstMinuteData[0].year
+    ) {
+      logStack('info', 'Every Enedis Analysis already synchronized')
+      return
+    } else if (firstEnedisMonthlyAnalysis) {
       logStack(
         'info',
-        'Doctype is empty, fetching history for one year maximum or until first enedis minute date'
+        'Doctype is partially completed, fetching all available history'
       )
-      const maximumDate = analysisDate.minus({ month: 12 }).startOf('month')
-      const diffInmonths = Math.ceil(
-        firstMinuteDate.diff(maximumDate, 'months').months
+      const firstEnedisMonthlyAnalysisDate = DateTime.fromObject({
+        year: firstEnedisMonthlyAnalysis[0].year,
+        month: firstEnedisMonthlyAnalysis[0].month,
+      }).startOf('month')
+      const diffInMonths = Math.ceil(
+        firstEnedisMonthlyAnalysisDate.diff(firstMinuteDate, 'months').months
       )
-      const limitDate = diffInmonths > 0 ? firstMinuteDate : maximumDate
-      const diffInMonthsWithLimitDate = Math.ceil(
-        analysisDate.diff(limitDate, 'months').months
-      )
-      for (let i = 1; i < diffInMonthsWithLimitDate; i++) {
-        const analysistoCreate = analysisDate.minus({ month: i })
+      for (let i = 1; i < diffInMonths; i++) {
+        const analysisToCreate = firstEnedisMonthlyAnalysisDate.minus({
+          month: i,
+        })
         const data = await getEnedisMonthAnalysisData(
           client,
-          analysistoCreate.month,
-          analysistoCreate.year
+          analysisToCreate.month,
+          analysisToCreate.year
         )
         if (data) {
-          await emas.createEnedisMonthlyAnalysisData(data)
+          await enedisMonthlyAnalysisDataService.createEnedisMonthlyAnalysisData(
+            data
+          )
         }
       }
     }
   } else {
+    // If user only have the last analysis available, fetch one year history
     logStack(
       'info',
-      'Enedis Minute is not activated or there is no data yet in this doctype'
+      'Doctype is empty, fetching history for one year maximum or until first enedis minute date'
     )
+    const maximumDate = analysisDate.minus({ month: 12 }).startOf('month')
+    const diffInMonths = Math.ceil(
+      firstMinuteDate.diff(maximumDate, 'months').months
+    )
+    const limitDate = diffInMonths > 0 ? firstMinuteDate : maximumDate
+    const diffInMonthsWithLimitDate = Math.ceil(
+      analysisDate.diff(limitDate, 'months').months
+    )
+    for (let i = 1; i < diffInMonthsWithLimitDate; i++) {
+      const analysisToCreate = analysisDate.minus({ month: i })
+      const data = await getEnedisMonthAnalysisData(
+        client,
+        analysisToCreate.month,
+        analysisToCreate.year
+      )
+      if (data) {
+        await enedisMonthlyAnalysisDataService.createEnedisMonthlyAnalysisData(
+          data
+        )
+      }
+    }
   }
 }
 
diff --git a/src/utils/utils.spec.ts b/src/utils/utils.spec.ts
index 1fe7e5944fb3ec95055b854411e2044d96d97af3..3fd5c09ab54600a8af02d745bd5cde7390449d59 100644
--- a/src/utils/utils.spec.ts
+++ b/src/utils/utils.spec.ts
@@ -10,6 +10,8 @@ import { FluidStatus } from 'models'
 import {
   formatListWithAnd,
   formatNumberValues,
+  formatOffPeakHours,
+  formatTwoDigits,
   getChallengeTitleWithLineReturn,
   getFluidName,
   getFluidType,
@@ -20,6 +22,10 @@ import {
   getMonthNameWithPrep,
   getSeason,
   isKonnectorActive,
+  isValidOffPeakHours,
+  parseOffPeakHours,
+  roundOffPeakHours,
+  splitOffPeakHours,
 } from './utils'
 
 describe('utils test', () => {
@@ -245,4 +251,112 @@ describe('utils test', () => {
       )
     })
   })
+
+  describe('isValidOffPeakHours', () => {
+    it('should return true for valid off-peak hours format', () => {
+      expect(isValidOffPeakHours('4H00-6H00')).toBe(true)
+      expect(isValidOffPeakHours('14H26-18H20')).toBe(true)
+      expect(isValidOffPeakHours('0H00-23H59')).toBe(true)
+    })
+
+    it('should return false for invalid off-peak hours format', () => {
+      expect(isValidOffPeakHours('invalid')).toBe(false) // unexpected string
+      expect(isValidOffPeakHours('2H30')).toBe(false) // Missing end of range
+      expect(isValidOffPeakHours('2H60-6H00')).toBe(false) // Minutes out of range
+      expect(isValidOffPeakHours('24H00-6H00')).toBe(false) // Hour out of range
+    })
+  })
+
+  describe('parseOffPeakHours', () => {
+    it('should return empty array', () => {
+      expect(parseOffPeakHours('')).toStrictEqual([])
+    })
+    it('should return single interval', () => {
+      expect(parseOffPeakHours('22H00-6H00')).toStrictEqual([
+        { start: { hour: 22, minute: 0 }, end: { hour: 6, minute: 0 } },
+      ])
+    })
+    it('should return two intervals', () => {
+      expect(parseOffPeakHours('3H00-8H00;13H30-16H30')).toStrictEqual([
+        { start: { hour: 3, minute: 0 }, end: { hour: 8, minute: 0 } },
+        { start: { hour: 13, minute: 30 }, end: { hour: 16, minute: 30 } },
+      ])
+    })
+    it('should return empty array because range is incomplete', () => {
+      expect(parseOffPeakHours('3H00-')).toStrictEqual([])
+    })
+    it('should return empty array because time is not valid', () => {
+      expect(parseOffPeakHours('51H00-98H99')).toStrictEqual([])
+    })
+  })
+  describe('formatTwoDigits', () => {
+    it('should return number with padding', () => {
+      expect(formatTwoDigits(5)).toBe('05')
+    })
+    it('should return number without padding', () => {
+      expect(formatTwoDigits(42)).toBe('42')
+    })
+  })
+  describe('formatOffPeakHours', () => {
+    it('should format correctly', () => {
+      expect(
+        formatOffPeakHours({
+          start: { hour: 2, minute: 0 },
+          end: { hour: 10, minute: 30 },
+        })
+      ).toBe('02H00-10H30')
+    })
+  })
+  describe('splitOffPeakHours', () => {
+    it('should split off-peak hours that cross midnight', () => {
+      const offPeakHours = [
+        { start: { hour: 22, minute: 0 }, end: { hour: 6, minute: 0 } },
+      ]
+      const expectedSplitOffPeakHours = [
+        { start: { hour: 22, minute: 0 }, end: { hour: 23, minute: 59 } },
+        { start: { hour: 0, minute: 0 }, end: { hour: 6, minute: 0 } },
+      ]
+      const result = splitOffPeakHours(offPeakHours)
+      expect(result).toEqual(expectedSplitOffPeakHours)
+    })
+    it('should not split off-peak hours that do not cross midnight', () => {
+      const offPeakHours = [
+        { start: { hour: 8, minute: 0 }, end: { hour: 12, minute: 0 } },
+      ]
+      const result = splitOffPeakHours(offPeakHours)
+      expect(result).toEqual(offPeakHours)
+    })
+  })
+  describe('roundOffPeakHours', () => {
+    it('rounds off-peak hours to the nearest half-hour', () => {
+      const offPeakHours = [
+        { start: { hour: 2, minute: 2 }, end: { hour: 10, minute: 58 } },
+        { start: { hour: 1, minute: 58 }, end: { hour: 11, minute: 2 } },
+        { start: { hour: 7, minute: 15 }, end: { hour: 14, minute: 45 } },
+        { start: { hour: 1, minute: 30 }, end: { hour: 3, minute: 0 } },
+      ]
+      const roundedOffPeakHours = roundOffPeakHours(offPeakHours)
+      expect(roundedOffPeakHours).toEqual([
+        { start: { hour: 2, minute: 0 }, end: { hour: 11, minute: 0 } },
+        { start: { hour: 2, minute: 0 }, end: { hour: 11, minute: 0 } },
+        { start: { hour: 7, minute: 30 }, end: { hour: 15, minute: 0 } },
+        { start: { hour: 1, minute: 30 }, end: { hour: 3, minute: 0 } },
+      ])
+    })
+    it('rounds off-peak hours to midnight', () => {
+      const offPeakHours = [
+        { start: { hour: 0, minute: 5 }, end: { hour: 4, minute: 0 } },
+        { start: { hour: 23, minute: 55 }, end: { hour: 4, minute: 0 } },
+        { start: { hour: 16, minute: 0 }, end: { hour: 23, minute: 55 } },
+        { start: { hour: 16, minute: 0 }, end: { hour: 0, minute: 5 } },
+      ]
+      const roundedOffPeakHours = roundOffPeakHours(offPeakHours)
+      expect(roundedOffPeakHours).toEqual([
+        { start: { hour: 0, minute: 0 }, end: { hour: 4, minute: 0 } },
+        { start: { hour: 0, minute: 0 }, end: { hour: 4, minute: 0 } },
+        { start: { hour: 16, minute: 0 }, end: { hour: 23, minute: 59 } },
+        { start: { hour: 16, minute: 0 }, end: { hour: 23, minute: 59 } },
+      ])
+    })
+  })
 })
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index c9a6cc15bc48281469b265b67584e60fb5736032..834c7463d74b9489b3aa142199de9e224d1d05d7 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -323,3 +323,149 @@ export const formatListWithAnd = (array: string[]) => {
     return array.join(', ') + ' et ' + lastElement
   }
 }
+
+export type OffPeakHours = {
+  start: { hour: number; minute: number }
+  end: { hour: number; minute: number }
+}
+
+/**
+ * Check if a string is a valid off-peak hour format
+ * @example
+ * isValidOffPeakHours("6H15-14H15") => true
+ * isValidOffPeakHours("68H78_12Hab") => false
+ */
+export const isValidOffPeakHours = (range: string) => {
+  const offPeakHoursRegex =
+    /^(0?\d|1\d|2[0-3])H[0-5]?\d-(0?\d|1\d|2[0-3])H[0-5]?\d$/
+  return offPeakHoursRegex.test(range)
+}
+
+/**
+ * Parse the string representation of off-peak hours from Enedis to an array of time ranges object
+ */
+export const parseOffPeakHours = (timeString: string): OffPeakHours[] => {
+  const timeRanges = timeString.split(';')
+  if (!timeRanges.every(range => isValidOffPeakHours(range))) {
+    console.error(`Error parsing time range "${timeString}"`)
+    return []
+  }
+
+  const intervals: OffPeakHours[] = []
+
+  for (const range of timeRanges) {
+    const [startStr, endStr] = range.split('-')
+
+    const startTime = DateTime.fromFormat(startStr, "H'H'mm")
+    const endTime = DateTime.fromFormat(endStr, "H'H'mm")
+
+    intervals.push({
+      start: { hour: startTime.hour, minute: startTime.minute },
+      end: { hour: endTime.hour, minute: endTime.minute },
+    })
+  }
+
+  return intervals
+}
+
+/**
+ * Format a number into a 2-digits string, padded with 0
+ * @example formatTwoDigits(5) returns "05"
+ */
+export const formatTwoDigits = (num: number): string => {
+  return num.toString().padStart(2, '0')
+}
+
+/**
+ * Format off-peak hours object into a human-readable string
+ * @example formatOffPeakHours({ start: { hour: 2, minute: 0 }, end: { hour: 10, minute: 0 }}) returns "02H00-10H00"
+ */
+export const formatOffPeakHours = (offPeakHours: OffPeakHours): string => {
+  const { start, end } = offPeakHours
+  const startTime = `${formatTwoDigits(start.hour)}H${formatTwoDigits(
+    start.minute
+  )}`
+  const endTime = `${formatTwoDigits(end.hour)}H${formatTwoDigits(end.minute)}`
+
+  return `${startTime}-${endTime}`
+}
+
+/**
+ * Split off-peak hours that cross midnight
+ * @example The range "22H00-6H00" becomes "22H00-23H59" and "0H00-6H00"
+ */
+export const splitOffPeakHours = (
+  offPeakHours: OffPeakHours[]
+): OffPeakHours[] => {
+  return offPeakHours.reduce((acc: OffPeakHours[], offPeakHour) => {
+    if (offPeakHour.start.hour > offPeakHour.end.hour) {
+      acc.push({
+        start: {
+          hour: offPeakHour.start.hour,
+          minute: offPeakHour.start.minute,
+        },
+        end: {
+          hour: 23,
+          minute: 59,
+        },
+      })
+      acc.push({
+        start: {
+          hour: 0,
+          minute: 0,
+        },
+        end: {
+          hour: offPeakHour.end.hour,
+          minute: offPeakHour.end.minute,
+        },
+      })
+    } else {
+      acc.push(offPeakHour)
+    }
+    return acc
+  }, [])
+}
+
+export const roundToNearestHalfHour = (
+  hour: number,
+  minute: number,
+  isEnd: boolean
+): { hour: number; minute: number } => {
+  let roundedMinute = Math.round(minute / 30) * 30 // Round to the nearest half-hour
+  let roundedHour = hour
+
+  // If rounding to the next hour (except for midnight), adjust the hour and reset the minute
+  if (roundedMinute === 60 && roundedHour !== 23) {
+    roundedHour += 1
+    roundedMinute = 0
+  }
+
+  // Don't round to midnight for the off-peak hours end, instead round to 23:59
+  if (
+    (roundedMinute === 60 && roundedHour === 23) ||
+    (roundedMinute === 0 && roundedHour === 0)
+  ) {
+    if (isEnd) {
+      roundedHour = 23
+      roundedMinute = 59
+    } else {
+      roundedHour = 0
+      roundedMinute = 0
+    }
+  }
+
+  return { hour: roundedHour, minute: roundedMinute }
+}
+
+/**
+ * Round off-peak hours to the nearest half-hour
+ * @example "6H50-14H50" becomes "7H00-15H00"
+ */
+export const roundOffPeakHours = (
+  offPeakHours: OffPeakHours[]
+): OffPeakHours[] => {
+  return offPeakHours.map(({ start, end }) => ({
+    start: roundToNearestHalfHour(start.hour, start.minute, false),
+    end: roundToNearestHalfHour(end.hour, end.minute, true),
+  }))
+}