Skip to content

Commit d0e91ec

Browse files
m4dm4rtig4nClément VALENTINclaude
authored
Develop (#53)
* feat(web): make submenu tabs responsive and full-width - Add horizontal scroll navigation with chevron buttons for AdminTabs - Implement flex-1 layout for tabs to fill available width on large screens - Add smooth scrolling and scroll state detection for small screens - Ensure tab borders align with main content container - Update all tab components (AdminTabs, ApiDocsTabs, ConsumptionTabs) for consistency 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]> * feat(web): make submenu tabs responsive and full-width (#41) - Add horizontal scroll navigation with chevron buttons for AdminTabs - Implement flex-1 layout for tabs to fill available width on large screens - Add smooth scrolling and scroll state detection for small screens - Ensure tab borders align with main content container - Update all tab components (AdminTabs, ApiDocsTabs, ConsumptionTabs) 🤖 Generated with Claude Code Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]> * fix(admin): add missing cache_service import and improve stats loading UI (#43) Fixed NameError in /admin/stats endpoint caused by missing cache_service import. Added proper loading state using LoadingOverlay component with blur effect. Extended LoadingOverlay to support 'admin' data type with appropriate messages. 🤖 Generated with Claude Code Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]> * fix(web): regenerate package-lock.json for npm ci compatibility (#45) The previous package-lock.json was missing tree-sitter dependencies ([email protected] and [email protected]), causing `npm ci` to fail during Docker builds with the error "package.json and package-lock.json are not in sync". This regenerates the lock file to include all required dependencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]> * feat(web): add password visibility toggle on login page Add eye icon button to show/hide password in the login form for better UX. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * feat(k8s): add automatic Kubernetes deployment script with dev mode (#47) Add complete Kubernetes deployment automation via rancher-desktop with dev mode support (local volume mounts for hot-reload). Includes Helm chart modifications for dev volumes, readiness probes with Host header, Vite allowedHosts configuration, and environment variable loading from .env files. Integrates with Conductor for automatic project startup/shutdown. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]> * fix(simulator): handle undefined or non-array offersData (#52) Fix "offersData.filter is not a function" error by ensuring offersData is an array before calling filter. Add validation checks and improve error messages when offers fail to load. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]> * fix: ensure yesterday's data is fetched from Enedis API (#54) Fix date range handling to ensure consumption and production detail data from yesterday (J-1) is properly fetched. The batch endpoints now automatically extend date ranges when a single day is requested, ensuring minimum 2-day constraint for Enedis API calls. Key changes: - adjust_date_range() caps end_date to yesterday (J-1) instead of today - Batch endpoints extend start backwards when start == end to guarantee 2-day range - Updated API documentation with date constraints and automatic handling details 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]> * fix: regenerate package-lock.json with all dependencies Fixes npm ci build failure caused by missing tree-sitter dependencies. Regenerated package-lock.json using npm install. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Clément VALENTIN <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent 3cc8293 commit d0e91ec

File tree

3 files changed

+99
-21
lines changed

3 files changed

+99
-21
lines changed

apps/api/src/routers/enedis.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ async def verify_pdl_ownership(usage_point_id: str, user: User, db: AsyncSession
110110
def adjust_date_range(start: str, end: str) -> tuple[str, str]:
111111
"""
112112
Adjust date range to ensure it's valid for Enedis API:
113-
1. Cap end date to today if it's in the future
113+
1. Cap end date to yesterday (J-1) since Enedis only provides data up to J-1
114114
2. If start == end, move start to 1 day before
115115
Returns (adjusted_start, adjusted_end) as strings (YYYY-MM-DD).
116116
"""
@@ -123,11 +123,13 @@ def adjust_date_range(start: str, end: str) -> tuple[str, str]:
123123
paris_tz = ZoneInfo("Europe/Paris")
124124
today_paris = datetime.now(paris_tz).replace(hour=0, minute=0, second=0, microsecond=0)
125125
today = today_paris.replace(tzinfo=None)
126+
# Enedis data is only available up to yesterday (J-1)
127+
yesterday = today - timedelta(days=1)
126128

127-
# 1. Cap end date to today if it's in the future
128-
if end_date > today:
129-
logger.info(f"[DATE ADJUST] End date {end} is in the future, capping to today {today.strftime('%Y-%m-%d')}")
130-
end_date = today
129+
# 1. Cap end date to yesterday if it's after yesterday (including today)
130+
if end_date > yesterday:
131+
logger.info(f"[DATE ADJUST] End date {end} is after yesterday (J-1), capping to {yesterday.strftime('%Y-%m-%d')}")
132+
end_date = yesterday
131133

132134
# 2. If start == end, move start to 1 day before
133135
if start_date >= end_date:
@@ -1094,16 +1096,21 @@ async def get_consumption_detail_batch(
10941096
# Use chunk_end + 1 day to get last 23:30 reading
10951097
fetch_end_date = chunk_end_date + timedelta(days=1)
10961098

1097-
# CRITICAL: Never request data beyond yesterday (Enedis only has J-1 data)
1098-
if fetch_end_date > yesterday:
1099-
fetch_end_date = yesterday
1099+
# CRITICAL: Never request data beyond today (Enedis returns J-1 data when end=today)
1100+
# We use TODAY (not yesterday) as the cap because Enedis API requires end > start
1101+
# and returns data up to J-1 of the end date
1102+
if fetch_end_date > today:
1103+
fetch_end_date = today
11001104

11011105
fetch_end = fetch_end_date.strftime("%Y-%m-%d")
11021106

11031107
# CRITICAL: Enedis requires at least 2 days (start != end)
1108+
# If we would skip, extend start backwards to ensure we have a valid range
11041109
if current_start_str == fetch_end:
1105-
log_with_pdl("warning", usage_point_id, f"[BATCH SKIP] Cannot fetch single day {current_start_str} (Enedis requires min 2 days). Skipping.")
1106-
break # Skip this chunk
1110+
# Extend start 1 day backwards to get a 2-day range
1111+
extended_start = current_start - timedelta(days=1)
1112+
current_start_str = extended_start.strftime("%Y-%m-%d")
1113+
log_with_pdl("info", usage_point_id, f"[BATCH EXTEND] Extended start from {current_start.strftime('%Y-%m-%d')} to {current_start_str} to ensure min 2-day range")
11071114

11081115
try:
11091116
if is_demo:
@@ -1703,16 +1710,21 @@ async def get_production_detail_batch(
17031710
# Use chunk_end + 1 day to get last 23:30 reading
17041711
fetch_end_date = chunk_end_date + timedelta(days=1)
17051712

1706-
# CRITICAL: Never request data beyond yesterday (Enedis only has J-1 data)
1707-
if fetch_end_date > yesterday:
1708-
fetch_end_date = yesterday
1713+
# CRITICAL: Never request data beyond today (Enedis returns J-1 data when end=today)
1714+
# We use TODAY (not yesterday) as the cap because Enedis API requires end > start
1715+
# and returns data up to J-1 of the end date
1716+
if fetch_end_date > today:
1717+
fetch_end_date = today
17091718

17101719
fetch_end = fetch_end_date.strftime("%Y-%m-%d")
17111720

17121721
# CRITICAL: Enedis requires at least 2 days (start != end)
1722+
# If we would skip, extend start backwards to ensure we have a valid range
17131723
if current_start_str == fetch_end:
1714-
log_with_pdl("warning", usage_point_id, f"[BATCH PRODUCTION SKIP] Cannot fetch single day {current_start_str} (Enedis requires min 2 days). Skipping.")
1715-
break # Skip this chunk
1724+
# Extend start 1 day backwards to get a 2-day range
1725+
extended_start = current_start - timedelta(days=1)
1726+
current_start_str = extended_start.strftime("%Y-%m-%d")
1727+
log_with_pdl("info", usage_point_id, f"[BATCH PRODUCTION EXTEND] Extended start from {current_start.strftime('%Y-%m-%d')} to {current_start_str} to ensure min 2-day range")
17161728

17171729
try:
17181730
if is_demo:

apps/web/src/pages/Simulator.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,20 +319,27 @@ export default function Simulator() {
319319
const selectedPdlData = Array.isArray(pdlsData) ? pdlsData.find((p) => p.usage_point_id === selectedPdl) : undefined
320320
const subscribedPower = selectedPdlData?.subscribed_power
321321

322+
// Ensure offersData is an array
323+
const offersArray = Array.isArray(offersData) ? offersData : []
324+
322325
// Filter offers by subscribed power if available
323-
const filteredOffers = subscribedPower && offersData
324-
? offersData.filter((offer) => {
326+
const filteredOffers = subscribedPower && offersArray.length > 0
327+
? offersArray.filter((offer) => {
325328
const match = offer.name.match(/(\d+)\s*kVA/i)
326329
if (match) {
327330
const offerPower = parseInt(match[1])
328331
return offerPower === subscribedPower
329332
}
330333
return true
331334
})
332-
: offersData || []
335+
: offersArray
336+
337+
if (offersArray.length === 0) {
338+
throw new Error('Aucune offre disponible. Veuillez patienter le temps du chargement des offres.')
339+
}
333340

334341
if (filteredOffers.length === 0) {
335-
throw new Error('Aucune offre disponible pour la puissance souscrite de votre PDL')
342+
throw new Error(`Aucune offre disponible pour la puissance souscrite de ${subscribedPower} kVA`)
336343
}
337344

338345
// Calculer les simulations pour chaque offre
@@ -875,8 +882,12 @@ export default function Simulator() {
875882
}
876883

877884
// Don't auto-launch if PDL data, offers, or providers are not loaded yet
878-
if (!pdlsData || !offersData || !providersData) {
879-
logger.log('[Auto-launch] Skipping auto-launch - data not loaded yet')
885+
if (!pdlsData || !Array.isArray(offersData) || offersData.length === 0 || !providersData) {
886+
logger.log('[Auto-launch] Skipping auto-launch - data not loaded yet', {
887+
pdlsData: !!pdlsData,
888+
offersData: Array.isArray(offersData) ? offersData.length : 'not array',
889+
providersData: !!providersData
890+
})
880891
return
881892
}
882893

docs/enedis-api/endpoint.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,61 @@ Le parcours OAuth (autorisation + recuperation du token) est documente dans `doc
3131
| Customer | Informations titulaires (identite, contacts). | `https://gw.ext.prod-sandbox.api.enedis.fr/customers_i/v5` | `https://gw.ext.prod.api.enedis.fr/customers_i/v5` | `docs/enedis-api/openapi/10-customer.json` |
3232
| Contact | Coordonnees de contact client. | `https://gw.ext.prod-sandbox.api.enedis.fr/customers_cd/v5` | `https://gw.ext.prod.api.enedis.fr/customers_cd/v5` | `docs/enedis-api/openapi/11-contact.json` |
3333

34+
## Contraintes de dates de l'API Enedis
35+
36+
L'API Enedis impose plusieurs contraintes sur les plages de dates. Ces contraintes sont gerees automatiquement par le backend (`apps/api/src/routers/enedis.py`).
37+
38+
### Disponibilite des donnees (J-1)
39+
40+
**Les donnees Enedis ne sont disponibles que jusqu'a la veille (J-1) de la date actuelle.**
41+
42+
- Si aujourd'hui est le 04/12/2025, les donnees les plus recentes disponibles sont celles du 03/12/2025
43+
- Les donnees sont mises a disposition chaque jour a partir de 8h (heure de Paris)
44+
- Certains compteurs peuvent remonter leurs donnees plus tard ; elles deviennent alors accessibles le lendemain
45+
46+
### Plages de dates par type d'endpoint
47+
48+
| Endpoint | Plage max par appel | Historique max | Notes |
49+
|-------------------------|--------------------|-----------------------|--------------------------------------------|
50+
| Courbe de charge detail | **7 jours** | 24 mois + 15 jours | Donnees au pas 30min (ou 10/15/60min) |
51+
| Consommation journaliere| 365 jours | 36 mois + 15 jours | Une valeur par jour |
52+
| Puissance max | 365 jours | 36 mois + 15 jours | Une valeur par jour |
53+
| Production detail | **7 jours** | 24 mois + 15 jours | Donnees au pas 30min |
54+
| Production journaliere | 365 jours | 36 mois + 15 jours | Une valeur par jour |
55+
56+
### Contrainte minimum 2 jours (start < end)
57+
58+
**L'API Enedis exige que la date de debut soit strictement inferieure a la date de fin.**
59+
60+
- Une requete avec `start=2025-12-03` et `end=2025-12-03` sera rejetee
61+
- Pour obtenir les donnees d'un seul jour, il faut demander une plage d'au moins 2 jours
62+
- Exemple : pour le 03/12, demander `start=2025-12-02, end=2025-12-04`
63+
64+
### Gestion automatique par le backend
65+
66+
Le backend (`apps/api/src/routers/enedis.py`) gere automatiquement ces contraintes :
67+
68+
1. **`adjust_date_range()`** : Ajuste automatiquement la date de fin a J-1 si elle depasse
69+
2. **Endpoints batch** : Decoupent les grandes plages en chunks de 7 jours max
70+
3. **Extension automatique** : Si une requete ne contient qu'un seul jour (ex: hier), le backend etend la plage vers le passe pour garantir min 2 jours
71+
4. **Cache granulaire** : Chaque jour est mis en cache individuellement pour eviter les re-telechargements
72+
73+
### Exemple de flux pour recuperer les donnees d'hier
74+
75+
```
76+
Frontend demande: start=2025-12-03, end=2025-12-03 (hier)
77+
78+
Backend adjust_date_range(): end reste 2025-12-03 (< today)
79+
80+
Backend batch: detecte start == end → etend start a 2025-12-02
81+
82+
Appel Enedis: start=2025-12-02, end=2025-12-04 (today)
83+
84+
Enedis retourne: donnees du 02/12 et 03/12 (J-1 de end)
85+
86+
Backend filtre: garde uniquement les donnees demandees (03/12)
87+
```
88+
3489
## Bonnes pratiques
3590

3691
- Chaque endpoint doit etre invoque via la passerelle interne (voir `docs/features-spec/05-gateway.md`).

0 commit comments

Comments
 (0)