{"openapi":"3.1.0","info":{"title":"Calendrier Tempo EDF","description":"Prévision des jours Tempo EDF avec alertes WhatsApp","version":"1.0.0"},"paths":{"/":{"get":{"summary":"Page Dashboard","description":"Page principale — dashboard des prévisions.","operationId":"page_dashboard__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Dashboard","description":"Page principale — dashboard des prévisions.","operationId":"page_dashboard__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/calendrier":{"get":{"summary":"Page Calendrier","description":"Page calendrier Tempo EDF — mois en cours (couleurs officielles + prévisions).\n\nCible SEO : 'calendrier tempo', 'calendrier tempo edf'.\nRendu serveur complet ; les autres mois ont leur propre URL /calendrier/AAAA-MM.\nLes anciennes URL ?month=&year= redirigent (301) vers l'URL canonique du mois.","operationId":"page_calendrier_calendrier_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Month"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Calendrier","description":"Page calendrier Tempo EDF — mois en cours (couleurs officielles + prévisions).\n\nCible SEO : 'calendrier tempo', 'calendrier tempo edf'.\nRendu serveur complet ; les autres mois ont leur propre URL /calendrier/AAAA-MM.\nLes anciennes URL ?month=&year= redirigent (301) vers l'URL canonique du mois.","operationId":"page_calendrier_calendrier_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Month"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/calendrier/{slug}":{"get":{"summary":"Page Calendrier Slug","description":"/calendrier/AAAA-MM (mois) ou /calendrier/AAAA-AAAA (saison, dates réelles).","operationId":"page_calendrier_slug_calendrier__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Calendrier Slug","description":"/calendrier/AAAA-MM (mois) ou /calendrier/AAAA-AAAA (saison, dates réelles).","operationId":"page_calendrier_slug_calendrier__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/calendrier-data":{"get":{"summary":"Api Calendrier Data","description":"API JSON du calendrier mensuel (mêmes données que les pages SSR).","operationId":"api_calendrier_data_api_calendrier_data_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Month"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/alertes":{"get":{"summary":"Page Alertes","description":"Page dédiée aux alertes WhatsApp gratuites.\n\nCible SEO : 'alerte tempo', 'alerte jour rouge tempo'.","operationId":"page_alertes_alertes_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Alertes","description":"Page dédiée aux alertes WhatsApp gratuites.\n\nCible SEO : 'alerte tempo', 'alerte jour rouge tempo'.","operationId":"page_alertes_alertes_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/admin":{"get":{"summary":"Page Admin","description":"Dashboard admin — performance et gestion.\n\nLa protection réelle est le mot de passe côté client + header Authorization\nsur chaque endpoint API admin. La page HTML seule ne contient aucune donnée.","operationId":"page_admin_admin_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/mentions-legales":{"get":{"summary":"Page Legal","description":"Page mentions légales et RGPD.","operationId":"page_legal_mentions_legales_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Legal","description":"Page mentions légales et RGPD.","operationId":"page_legal_mentions_legales_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/a-propos":{"get":{"summary":"Page A Propos","description":"Page À propos — méthodologie, transparence, E-E-A-T.\n\nCible SEO : renforce la confiance et l'autorité du site.\nEssentiel pour les critères E-E-A-T de Google (Experience, Expertise, Authority, Trust).","operationId":"page_a_propos_a_propos_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page A Propos","description":"Page À propos — méthodologie, transparence, E-E-A-T.\n\nCible SEO : renforce la confiance et l'autorité du site.\nEssentiel pour les critères E-E-A-T de Google (Experience, Expertise, Authority, Trust).","operationId":"page_a_propos_a_propos_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/tarif-tempo-edf":{"get":{"summary":"Page Tarif Tempo","description":"Grille tarifaire Tempo EDF en vigueur (source : site_facts.TARIFS).\n\nCible SEO : 'tarif tempo edf'. Rendu SSG-like (contenu statique, cache 1 h).","operationId":"page_tarif_tempo_tarif_tempo_edf_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Tarif Tempo","description":"Grille tarifaire Tempo EDF en vigueur (source : site_facts.TARIFS).\n\nCible SEO : 'tarif tempo edf'. Rendu SSG-like (contenu statique, cache 1 h).","operationId":"page_tarif_tempo_tarif_tempo_edf_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/couleur-tempo-demain":{"get":{"summary":"Page Couleur Demain","description":"Couleur Tempo EDF de demain : officielle si publiée, sinon prévision signalée.\n\nCible SEO : 'couleur tempo demain', 'edf tempo couleur du lendemain'.\nSSR à chaque requête (cache 2 min) : la couleur change en fin de matinée.","operationId":"page_couleur_demain_couleur_tempo_demain_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Couleur Demain","description":"Couleur Tempo EDF de demain : officielle si publiée, sinon prévision signalée.\n\nCible SEO : 'couleur tempo demain', 'edf tempo couleur du lendemain'.\nSSR à chaque requête (cache 2 min) : la couleur change en fin de matinée.","operationId":"page_couleur_demain_couleur_tempo_demain_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/api-tempo":{"get":{"summary":"Page Api Tempo","description":"Documentation de l'API publique JSON (formats lus dans les routes /api/*).","operationId":"page_api_tempo_api_tempo_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Api Tempo","description":"Documentation de l'API publique JSON (formats lus dans les routes /api/*).","operationId":"page_api_tempo_api_tempo_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/widget":{"get":{"summary":"Page Widget","description":"Documentation du widget + générateur de code (page indexable, contenu statique).","operationId":"page_widget_widget_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Widget","description":"Documentation du widget + générateur de code (page indexable, contenu statique).","operationId":"page_widget_widget_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/admin/widget-hosts":{"get":{"summary":"Admin Widget Hosts","description":"Sites qui affichent le widget : compteur (jour Paris, nom d'hôte du Referer) tenu au\nbord par le Worker (cloudflare/worker.ts, stockage du Durable Object). L'app vérifie\nl'accès admin ; sur Cloudflare, le Worker remplace cette réponse par les compteurs.\nHors Cloudflare (local, tests) : available = False.","operationId":"admin_widget_hosts_admin_widget_hosts_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/usage-counts":{"get":{"summary":"Admin Usage Counts","description":"Compteurs d'utilisation bruts (API publique, domotique, widget) tenus au bord par le\nWorker (cloudflare/usage_counter.ts) : lignes (jour, type, cible, client, nombre), aucune\ndonnée personnelle. L'app vérifie l'accès admin ; sur Cloudflare, le Worker remplace cette\nréponse par les compteurs (lus aussi chaque nuit par usage.py). Hors Cloudflare : available = False.","operationId":"admin_usage_counts_admin_usage_counts_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/domotique":{"get":{"summary":"Page Domotique","description":"Home Assistant, Jeedom et Domoticz sur l'API publique (champs réels de /api/*).","operationId":"page_domotique_domotique_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Domotique","description":"Home Assistant, Jeedom et Domoticz sur l'API publique (champs réels de /api/*).","operationId":"page_domotique_domotique_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/presse":{"get":{"summary":"Page Presse","description":"Dossier de presse (texte : docs/growth/2026-10-05-presse-copy.md partie A, revues du tour 1).\n\nTaux : même source, même règle et même phrase que /methodologie (_reference_season,\ntemplates/_taux_reference.html), texte de repli sans taux sinon. Ligne « Alertes WhatsApp »\nseulement hors mode test (même condition que l'envoi : alerts.is_whatsapp_test_mode).","operationId":"page_presse_presse_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Presse","description":"Dossier de presse (texte : docs/growth/2026-10-05-presse-copy.md partie A, revues du tour 1).\n\nTaux : même source, même règle et même phrase que /methodologie (_reference_season,\ntemplates/_taux_reference.html), texte de repli sans taux sinon. Ligne « Alertes WhatsApp »\nseulement hors mode test (même condition que l'envoi : alerts.is_whatsapp_test_mode).","operationId":"page_presse_presse_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/presse/{slug}":{"get":{"summary":"Page Presse Communique","description":"Texte intégral d'un communiqué publié (arbitrage presse du 2026-10-05, point 13).\nSlug inconnu : 404. Aucun communiqué publié tant que site_facts.PRESSE_COMMUNIQUES est vide.","operationId":"page_presse_communique_presse__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Presse Communique","description":"Texte intégral d'un communiqué publié (arbitrage presse du 2026-10-05, point 13).\nSlug inconnu : 404. Aucun communiqué publié tant que site_facts.PRESSE_COMMUNIQUES est vide.","operationId":"page_presse_communique_presse__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/methodologie":{"get":{"summary":"Page Methodologie","description":"Méthodologie de prévision et chiffres de performance, avec leur nature exacte.\n\nTaux publié = dernière saison complète, calculé sur les prévisions réellement émises\n(décision fondateur du 2026-09-30), même source que /historique-previsions.","operationId":"page_methodologie_methodologie_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Methodologie","description":"Méthodologie de prévision et chiffres de performance, avec leur nature exacte.\n\nTaux publié = dernière saison complète, calculé sur les prévisions réellement émises\n(décision fondateur du 2026-09-30), même source que /historique-previsions.","operationId":"page_methodologie_methodologie_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/historique-previsions":{"get":{"summary":"Page Historique Previsions","description":"Historique de nos prévisions J-2 à J-5 face aux couleurs EDF, saison en cours.\n\nSSR à chaque requête (données : _history_page_context, cache mémoire invalidé à chaque\nécriture affichée + HTTP 10 min) : change à chaque confirmation EDF.","operationId":"page_historique_previsions_historique_previsions_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Historique Previsions","description":"Historique de nos prévisions J-2 à J-5 face aux couleurs EDF, saison en cours.\n\nSSR à chaque requête (données : _history_page_context, cache mémoire invalidé à chaque\nécriture affichée + HTTP 10 min) : change à chaque confirmation EDF.","operationId":"page_historique_previsions_historique_previsions_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/historique-previsions.csv":{"get":{"summary":"Historique Previsions Csv","description":"Export CSV complet (toutes saisons), séparateur « ; », UTF-8 avec BOM.","operationId":"historique_previsions_csv_historique_previsions_csv_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Historique Previsions Csv","description":"Export CSV complet (toutes saisons), séparateur « ; », UTF-8 avec BOM.","operationId":"historique_previsions_csv_historique_previsions_csv_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/historique-previsions/{saison}":{"get":{"summary":"Page Historique Previsions Saison","description":"Historique d'une saison passée ; la saison en cours redirige vers /historique-previsions.","operationId":"page_historique_previsions_saison_historique_previsions__saison__get","parameters":[{"name":"saison","in":"path","required":true,"schema":{"type":"string","title":"Saison"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Historique Previsions Saison","description":"Historique d'une saison passée ; la saison en cours redirige vers /historique-previsions.","operationId":"page_historique_previsions_saison_historique_previsions__saison__get","parameters":[{"name":"saison","in":"path","required":true,"schema":{"type":"string","title":"Saison"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/blog/":{"get":{"summary":"Page Blog Index","description":"Page index du blog — liste les articles publiés.","operationId":"page_blog_index_blog__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}},"head":{"summary":"Page Blog Index","description":"Page index du blog — liste les articles publiés.","operationId":"page_blog_index_blog__get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/blog/{slug}":{"get":{"summary":"Page Blog Article","description":"Page d'un article de blog individuel.","operationId":"page_blog_article_blog__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"head":{"summary":"Page Blog Article","description":"Page d'un article de blog individuel.","operationId":"page_blog_article_blog__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/manage/{token}":{"get":{"summary":"Page Manage","description":"Page de gestion des préférences (lien envoyé dans chaque message WhatsApp).","operationId":"page_manage_manage__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/manifest.json":{"get":{"summary":"Manifest Json","description":"Web App Manifest pour 'Ajouter à l'écran d'accueil' et favoris.","operationId":"manifest_json_manifest_json_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/robots.txt":{"get":{"summary":"Robots Txt","description":"Robots.txt pour les moteurs de recherche et crawlers IA.","operationId":"robots_txt_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/sitemap.xml":{"get":{"summary":"Sitemap Xml","description":"Sitemap servi hors event loop et mis en cache comme les autres pages (audit Ahrefs\n2026-10-08 : ~1 s par requête non cachée au bord, lectures Neon synchrones).","operationId":"sitemap_xml_sitemap_xml_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms.txt":{"get":{"summary":"Llms Txt","description":"LLMs.txt — standard émergent pour la découverte par les LLMs.\n\nFormat spec : https://llmstxt.org/\nH1 (requis) → blockquote résumé → sections H2 avec listes de liens.","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/llms-full.txt":{"get":{"summary":"Llms Full Txt","description":"LLMs-full.txt — contenu complet en Markdown brut (tableaux intacts).\n\nFaits essentiels, FAQ, puis le Markdown source de chaque article publié.","operationId":"llms_full_txt_llms_full_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/feed.xml":{"get":{"summary":"Rss Feed","description":"Flux RSS des articles du blog — enrichi avec content:encoded et categories.","operationId":"rss_feed_feed_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/27597da43533cb9cb20af2aac8debb0a.txt":{"get":{"summary":"Indexnow Key File","description":"Fichier de vérification IndexNow (requis par le protocole).","operationId":"indexnow_key_file_27597da43533cb9cb20af2aac8debb0a_txt_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/api/indexnow/ping":{"get":{"summary":"Indexnow Ping","description":"Ping IndexNow pour notifier Bing/Yandex d'une mise à jour (admin uniquement).\n\nUsage admin : GET /api/indexnow/ping?url=https://www.calendrier-tempo.fr/blog/slug\navec le header Authorization: Bearer <mot de passe admin>.\nSi url absent, notifie les pages principales. Seules les URL du site sont acceptées.","operationId":"indexnow_ping_api_indexnow_ping_get","parameters":[{"name":"url","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Url"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","description":"Health check endpoint — répond 200 immédiatement (Cloud Run startup probe).\n\nRetourne le statut DB pour le monitoring, mais ne bloque pas le démarrage.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Health","description":"Health check endpoint — répond 200 immédiatement (Cloud Run startup probe).\n\nRetourne le statut DB pour le monitoring, mais ne bloque pas le démarrage.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/keepalive":{"get":{"summary":"Keepalive","description":"Endpoint pour services de keepalive externes (UptimeRobot, cron-job.org).\n\nRésilience Autoscale : maintient l'instance éveillée et déclenche\nle rattrapage des tâches manquées si nécessaire (cooldown 5 min).\n\nConfigurer un service externe pour pinger cette URL toutes les 5 min\nentre 6h et 23h (heure Paris) afin de garantir l'exécution des\ntâches planifiées par APScheduler.","operationId":"keepalive_keepalive_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/today":{"get":{"summary":"Api Today","description":"Couleur Tempo du jour via l'API officielle (cache 2 min).","operationId":"api_today_api_today_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/tomorrow":{"get":{"summary":"Api Tomorrow","description":"Couleur Tempo de demain (cache 2 min).","operationId":"api_tomorrow_api_tomorrow_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/remaining":{"get":{"summary":"Api Remaining","description":"Jours restants par couleur pour la saison en cours (cache 2 min).","operationId":"api_remaining_api_remaining_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/predictions":{"get":{"summary":"Api Predictions","description":"Retourne les prédictions J+1 → J+15 depuis la DB (source unique).\n\nFix v5 #1/#2 : lit les prédictions stockées par le scheduler (18h)\nau lieu de recalculer à chaque visite. Tous les visiteurs voient\nla même chose. Cache mémoire court (5min) pour réduire les accès DB.\n\nFallback : si aucune prédiction n'existe en DB (premier lancement),\ngénère à la volée et stocke pour les visiteurs suivants.","operationId":"api_predictions_api_predictions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/history":{"get":{"summary":"Api History","description":"Historique des couleurs reelles des N derniers jours.\nFix #10 audit v4 : cap a 365 jours pour eviter scan complet.","operationId":"api_history_api_history_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/performance":{"get":{"summary":"Api Performance","description":"Métriques de performance complètes (admin).\n\nArgs:\n    season: saison au format \"YYYY-YYYY\" (ex: \"2025-2026\"). Absente = saison en cours.","operationId":"api_performance_api_performance_get","parameters":[{"name":"season","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Season"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/performance/badge":{"get":{"summary":"Api Performance Badge","description":"Taux de réussite publié (décision fondateur du 2026-09-30).\n\nDernière saison complète (31 août passé), prévisions J+2 à J+5, taux sur les\nprévisions réellement émises. Jamais un taux sur 30 jours (en septembre, presque\ntous les jours sont bleus : un 100 % ne voudrait rien dire).","operationId":"api_performance_badge_api_performance_badge_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/performance/csv":{"get":{"summary":"Api Performance Csv","description":"Export CSV des performances mensuelles (admin only).","operationId":"api_performance_csv_api_performance_csv_get","parameters":[{"name":"month","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Month"}},{"name":"year","in":"query","required":false,"schema":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning":{"get":{"summary":"Api Learning","description":"Journal d'apprentissage — patterns d'erreurs et corrections actives (admin).","operationId":"api_learning_api_learning_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/health":{"get":{"summary":"Api Learning Health","description":"Métriques de santé du système d'apprentissage (admin).","operationId":"api_learning_health_api_learning_health_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subscribe":{"post":{"summary":"Api Subscribe","description":"Inscription aux alertes WhatsApp (Fix #16 : rate limiting + CSRF check).","operationId":"api_subscribe_api_subscribe_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_api_subscribe_api_subscribe_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/subscribe/undo":{"post":{"summary":"Api Subscribe Undo","description":"« Corriger le numéro » : supprime l'inscription qui vient d'être créée (jeton de /api/subscribe).\n\nSans jeton valide (absent, falsifié, expiré, déjà utilisé) : 400 générique, rien n'est supprimé.","operationId":"api_subscribe_undo_api_subscribe_undo_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_api_subscribe_undo_api_subscribe_undo_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/unsubscribe":{"post":{"summary":"Api Unsubscribe","description":"Désinscription des alertes WhatsApp.","operationId":"api_unsubscribe_api_unsubscribe_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_api_unsubscribe_api_unsubscribe_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/resend-manage-link":{"post":{"summary":"Api Resend Manage Link","description":"Renvoie le lien de gestion par WhatsApp (pour les utilisateurs déjà inscrits).\n\nRépond immédiatement, l'envoi WhatsApp se fait en arrière-plan pour éviter\nles timeouts (send_whatsapp peut prendre 30-45s avec les retries).","operationId":"api_resend_manage_link_api_resend_manage_link_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_api_resend_manage_link_api_resend_manage_link_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/webhook/whatsapp":{"get":{"summary":"Whatsapp Webhook Verify","description":"Vérification du webhook Meta WhatsApp (challenge handshake).\n\nMeta envoie un GET avec hub.mode, hub.verify_token, hub.challenge.\nOn répond avec le challenge si le verify_token correspond.","operationId":"whatsapp_webhook_verify_api_webhook_whatsapp_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Whatsapp Webhook Incoming","description":"Webhook Meta WhatsApp pour messages entrants + statuts de livraison.\n\nMeta envoie le payload JSON avec les messages reçus ET les statuts\nde livraison (sent/delivered/read/failed).\nBUG-01 QA : endpoint pour l'opt-out par message.","operationId":"whatsapp_webhook_incoming_api_webhook_whatsapp_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/users/stats":{"get":{"summary":"Api User Stats","description":"Stats utilisateurs (admin).","operationId":"api_user_stats_api_users_stats_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/manage/{token}":{"get":{"summary":"Api Manage Get","description":"Récupère les préférences d'un utilisateur via son token.","operationId":"api_manage_get_api_manage__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Api Manage Update","description":"Met à jour les préférences via le token de gestion.","operationId":"api_manage_update_api_manage__token__post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_api_manage_update_api_manage__token__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/manage/{token}/unsubscribe":{"post":{"summary":"Api Manage Unsubscribe","description":"Désinscription via le token de gestion (pas besoin de numéro).","operationId":"api_manage_unsubscribe_api_manage__token__unsubscribe_post","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/run-task":{"post":{"summary":"Admin Run Task","description":"Execute une tache du scheduler manuellement.\nFix #8 audit v4 : ajout check CSRF.","operationId":"admin_run_task_admin_run_task_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_admin_run_task_admin_run_task_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/scheduler-status":{"get":{"summary":"Admin Scheduler Status","description":"Etat des taches planifiees du scheduler.","operationId":"admin_scheduler_status_admin_scheduler_status_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/audience":{"get":{"summary":"Admin Audience","description":"Audience archivée (Umami → audience_daily, v28) pour l'onglet Abonnés :\nlignes par jour, inscriptions réelles, totaux 7 j / 30 j, tops 30 j.","operationId":"admin_audience_admin_audience_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/authority":{"get":{"summary":"Admin Authority","description":"Bloc « Autorité » (Search Console → search_daily / index_status, v29) :\ntotaux 7 j / 28 j vs période précédente, tops 28 j, indexation des pages\nclés, nouveaux domaines référents (audience_daily).","operationId":"admin_authority_admin_authority_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":60,"title":"Days"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/usage":{"get":{"summary":"Admin Usage","description":"Bloc « Utilisation » (onglet Abonnés, sous « Audience ») : appels API externes, installations\nHome Assistant estimées, téléchargements et clics HACS, widget ; 7 et 28 jours vs période\nprécédente, lus dans usage_daily (v30, archivé chaque nuit depuis le bord).","operationId":"admin_usage_admin_usage_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/weights-history":{"get":{"summary":"Admin Weights History","description":"Historique des versions de poids.","operationId":"admin_weights_history_admin_weights_history_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/sms-logs":{"get":{"summary":"Admin Sms Logs","description":"Derniers messages envoyés.","operationId":"admin_sms_logs_admin_sms_logs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/whatsapp-diagnostic":{"get":{"summary":"Admin Whatsapp Diagnostic","description":"Diagnostic des templates WhatsApp — vérifie la cohérence code↔Meta.\n\nListe chaque template avec le nom, le nombre de paramètres envoyés par le code,\net le statut de la configuration Meta (token présent, numéro configuré).","operationId":"admin_whatsapp_diagnostic_admin_whatsapp_diagnostic_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/db-diagnostic":{"get":{"summary":"Admin Db Diagnostic","description":"Diagnostic rapide de l'état des données en base.\n\nPermet de vérifier si les predictions, actuals et performance\nexistent pour les dates récentes. Utile pour déboguer les problèmes\nde données manquantes en production.","operationId":"admin_db_diagnostic_admin_db_diagnostic_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/agent-reports":{"get":{"summary":"Admin Agent Reports","description":"Rapports des agents SEO et Backlinks.","operationId":"admin_agent_reports_admin_agent_reports_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/validate-articles":{"get":{"summary":"Admin Validate Articles","description":"Valide tous les articles de blog via validate_article.py.","operationId":"admin_validate_articles_admin_validate_articles_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/subscribers":{"get":{"summary":"Admin Subscribers","description":"Liste des abonnés WhatsApp (4 derniers chiffres uniquement).","operationId":"admin_subscribers_admin_subscribers_get","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_admin_run_task_admin_run_task_post":{"properties":{"task":{"type":"string","title":"Task"}},"type":"object","required":["task"],"title":"Body_admin_run_task_admin_run_task_post"},"Body_api_manage_update_api_manage__token__post":{"properties":{"seuil_rouge":{"type":"integer","title":"Seuil Rouge","default":70},"delai":{"type":"integer","title":"Delai","default":3},"alerte_blanc":{"type":"boolean","title":"Alerte Blanc","default":false},"recap_hebdo":{"type":"boolean","title":"Recap Hebdo","default":false},"heure_envoi":{"type":"string","title":"Heure Envoi","default":"matin"}},"type":"object","title":"Body_api_manage_update_api_manage__token__post"},"Body_api_resend_manage_link_api_resend_manage_link_post":{"properties":{"phone":{"type":"string","title":"Phone"}},"type":"object","required":["phone"],"title":"Body_api_resend_manage_link_api_resend_manage_link_post"},"Body_api_subscribe_api_subscribe_post":{"properties":{"phone":{"type":"string","title":"Phone"},"seuil_rouge":{"type":"integer","title":"Seuil Rouge","default":70},"delai":{"type":"integer","title":"Delai","default":3},"alerte_blanc":{"type":"boolean","title":"Alerte Blanc","default":false},"recap_hebdo":{"type":"boolean","title":"Recap Hebdo","default":true},"heure_envoi":{"type":"string","title":"Heure Envoi","default":"matin"},"website":{"type":"string","title":"Website","default":""},"t":{"type":"string","title":"T","default":""}},"type":"object","required":["phone"],"title":"Body_api_subscribe_api_subscribe_post"},"Body_api_subscribe_undo_api_subscribe_undo_post":{"properties":{"token":{"type":"string","title":"Token","default":""}},"type":"object","title":"Body_api_subscribe_undo_api_subscribe_undo_post"},"Body_api_unsubscribe_api_unsubscribe_post":{"properties":{"phone":{"type":"string","title":"Phone"}},"type":"object","required":["phone"],"title":"Body_api_unsubscribe_api_unsubscribe_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}