hass.tibber_prices/developer/period-calculation-theory.html
github-actions[bot] e9aea64a2e deploy: 6898c126e3
2025-12-06 01:42:39 +00:00

825 lines
No EOL
163 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper plugin-docs plugin-id-default docs-version-current docs-doc-page docs-doc-id-period-calculation-theory" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v3.9.2">
<title data-rh="true">Period Calculation Theory | Tibber Prices - Developer Guide</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:image" content="https://jpawlowski.github.io/hass.tibber_prices/developer/img/social-card.png"><meta data-rh="true" name="twitter:image" content="https://jpawlowski.github.io/hass.tibber_prices/developer/img/social-card.png"><meta data-rh="true" property="og:url" content="https://jpawlowski.github.io/hass.tibber_prices/developer/period-calculation-theory"><meta data-rh="true" property="og:locale" content="en"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Period Calculation Theory | Tibber Prices - Developer Guide"><meta data-rh="true" name="description" content="Overview"><meta data-rh="true" property="og:description" content="Overview"><link data-rh="true" rel="icon" href="/hass.tibber_prices/developer/img/logo.svg"><link data-rh="true" rel="canonical" href="https://jpawlowski.github.io/hass.tibber_prices/developer/period-calculation-theory"><link data-rh="true" rel="alternate" href="https://jpawlowski.github.io/hass.tibber_prices/developer/period-calculation-theory" hreflang="en"><link data-rh="true" rel="alternate" href="https://jpawlowski.github.io/hass.tibber_prices/developer/period-calculation-theory" hreflang="x-default"><script data-rh="true" type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Period Calculation Theory","item":"https://jpawlowski.github.io/hass.tibber_prices/developer/period-calculation-theory"}]}</script><link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;family=Space+Grotesk:wght@500;600;700&amp;display=swap"><link rel="stylesheet" href="/hass.tibber_prices/developer/assets/css/styles.be4f3d68.css">
<script src="/hass.tibber_prices/developer/assets/js/runtime~main.09b7b31a.js" defer="defer"></script>
<script src="/hass.tibber_prices/developer/assets/js/main.5eb4cf38.js" defer="defer"></script>
</head>
<body class="navigation-with-keyboard">
<svg style="display: none;"><defs>
<symbol id="theme-svg-external-link" viewBox="0 0 24 24"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"/></symbol>
</defs></svg>
<script>!function(){var t=function(){try{return new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}}()||function(){try{return window.localStorage.getItem("theme")}catch(t){}}();document.documentElement.setAttribute("data-theme",t||(window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light")),document.documentElement.setAttribute("data-theme-choice",t||"system")}(),function(){try{const c=new URLSearchParams(window.location.search).entries();for(var[t,e]of c)if(t.startsWith("docusaurus-data-")){var a=t.replace("docusaurus-data-","data-");document.documentElement.setAttribute(a,e)}}catch(t){}}()</script><div id="__docusaurus"><link rel="preload" as="image" href="/hass.tibber_prices/developer/img/logo.svg"><div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="theme-layout-navbar navbar navbar--fixed-top"><div class="navbar__inner"><div class="theme-layout-navbar-left navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/hass.tibber_prices/developer/"><div class="navbar__logo"><img src="/hass.tibber_prices/developer/img/logo.svg" alt="Tibber Prices Integration Logo" class="themedComponent_mlkZ themedComponent--light_NVdE"><img src="/hass.tibber_prices/developer/img/logo.svg" alt="Tibber Prices Integration Logo" class="themedComponent_mlkZ themedComponent--dark_xIcU"></div><b class="navbar__title text--truncate">Tibber Prices HA</b></a><a class="navbar__item navbar__link" href="/hass.tibber_prices/developer/intro">Developer Guide</a><a href="https://jpawlowski.github.io/hass.tibber_prices/user/" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">User Docs<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></div><div class="theme-layout-navbar-right navbar__items navbar__items--right"><a class="navbar__item navbar__link" href="/hass.tibber_prices/developer/period-calculation-theory">Next 🚧</a><a href="https://github.com/jpawlowski/hass.tibber_prices" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">GitHub<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a><div class="toggle_vylO colorModeToggle_DEke"><button class="clean-btn toggleButton_gllP toggleButtonDisabled_aARS" type="button" disabled="" title="system mode" aria-label="Switch between dark and light mode (currently system mode)"><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP lightToggleIcon_pyhR"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP darkToggleIcon_wfgR"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" class="toggleIcon_g3eP systemToggleIcon_QzmC"><path fill="currentColor" d="m12 21c4.971 0 9-4.029 9-9s-4.029-9-9-9-9 4.029-9 9 4.029 9 9 9zm4.95-13.95c1.313 1.313 2.05 3.093 2.05 4.95s-0.738 3.637-2.05 4.95c-1.313 1.313-3.093 2.05-4.95 2.05v-14c1.857 0 3.637 0.737 4.95 2.05z"></path></svg></button></div><div class="navbarSearchContainer_Bca1"><div class="navbar__search"><span aria-label="expand searchbar" role="button" class="search-icon" tabindex="0"></span><input id="search_input_react" type="search" placeholder="Loading..." aria-label="Search" class="navbar__search-input search-bar" disabled=""></div></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="theme-layout-main main-wrapper mainWrapper_z2l0"><div class="docsWrapper_hBAB"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docRoot_UBD9"><aside class="theme-doc-sidebar-container docSidebarContainer_YfHR"><div class="sidebarViewport_aRkj"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/hass.tibber_prices/developer/intro"><span title="Developer Documentation" class="linkLabel_WmDU">Developer Documentation</span></a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_byQd menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="true" href="/hass.tibber_prices/developer/architecture"><span title="🏗️ Architecture" class="categoryLinkLabel_W154">🏗️ Architecture</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/architecture"><span title="Architecture" class="linkLabel_WmDU">Architecture</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/timer-architecture"><span title="Timer Architecture" class="linkLabel_WmDU">Timer Architecture</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/caching-strategy"><span title="Caching Strategy" class="linkLabel_WmDU">Caching Strategy</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/api-reference"><span title="API Reference" class="linkLabel_WmDU">API Reference</span></a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_byQd menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="true" href="/hass.tibber_prices/developer/setup"><span title="💻 Development" class="categoryLinkLabel_W154">💻 Development</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/setup"><span title="Development Setup" class="linkLabel_WmDU">Development Setup</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/coding-guidelines"><span title="Coding Guidelines" class="linkLabel_WmDU">Coding Guidelines</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/critical-patterns"><span title="Critical Behavior Patterns - Testing Guide" class="linkLabel_WmDU">Critical Behavior Patterns - Testing Guide</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/debugging"><span title="Debugging Guide" class="linkLabel_WmDU">Debugging Guide</span></a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_byQd menu__link menu__link--sublist menu__link--sublist-caret menu__link--active" role="button" aria-expanded="true" href="/hass.tibber_prices/developer/period-calculation-theory"><span title="📐 Advanced Topics" class="categoryLinkLabel_W154">📐 Advanced Topics</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/hass.tibber_prices/developer/period-calculation-theory"><span title="Period Calculation Theory" class="linkLabel_WmDU">Period Calculation Theory</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/refactoring-guide"><span title="Refactoring Guide" class="linkLabel_WmDU">Refactoring Guide</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/performance"><span title="Performance Optimization" class="linkLabel_WmDU">Performance Optimization</span></a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_byQd menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="true" href="/hass.tibber_prices/developer/contributing"><span title="📝 Contributing" class="categoryLinkLabel_W154">📝 Contributing</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/contributing"><span title="Contributing Guide" class="linkLabel_WmDU">Contributing Guide</span></a></li></ul></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="categoryLink_byQd menu__link menu__link--sublist menu__link--sublist-caret" role="button" aria-expanded="true" href="/hass.tibber_prices/developer/release-management"><span title="🚀 Release" class="categoryLinkLabel_W154">🚀 Release</span></a></div><ul class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/release-management"><span title="Release Notes Generation" class="linkLabel_WmDU">Release Notes Generation</span></a></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link" tabindex="0" href="/hass.tibber_prices/developer/testing"><span title="Testing" class="linkLabel_WmDU">Testing</span></a></li></ul></li></ul></nav><button type="button" title="Collapse sidebar" aria-label="Collapse sidebar" class="button button--secondary button--outline collapseSidebarButton_PEFL"><svg width="20" height="20" aria-hidden="true" class="collapseSidebarButtonIcon_kv0_"><g fill="#7a7a7a"><path d="M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"></path><path d="M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"></path></g></svg></button></div></div></aside><main class="docMainContainer_TBSr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="theme-doc-version-banner alert alert--warning margin-bottom--md" role="alert"><div>This is unreleased documentation for <!-- -->Tibber Prices - Developer Guide<!-- --> <b>Next 🚧</b> version.</div><div class="margin-top--md">For up-to-date documentation, see the <b><a href="/hass.tibber_prices/developer/period-calculation-theory">latest version</a></b> (<!-- -->Next 🚧<!-- -->).</div></div><div class="docItemContainer_Djhp"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer_Z_bl" aria-label="Breadcrumbs"><ul class="breadcrumbs"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/hass.tibber_prices/developer/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_YNFT"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li class="breadcrumbs__item"><span class="breadcrumbs__link">📐 Advanced Topics</span></li><li class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link">Period Calculation Theory</span></li></ul></nav><span class="theme-doc-version-badge badge badge--secondary">Version: Next 🚧</span><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>Period Calculation Theory</h1></header>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no"></a></h2>
<p>This document explains the mathematical foundations and design decisions behind the period calculation algorithm, particularly focusing on the interaction between <strong>Flexibility (Flex)</strong>, <strong>Minimum Distance from Average</strong>, and <strong>Relaxation Strategy</strong>.</p>
<p><strong>Target Audience:</strong> Developers maintaining or extending the period calculation logic.</p>
<p><strong>Related Files:</strong></p>
<ul>
<li class=""><code>coordinator/period_handlers/core.py</code> - Main calculation entry point</li>
<li class=""><code>coordinator/period_handlers/level_filtering.py</code> - Flex and distance filtering</li>
<li class=""><code>coordinator/period_handlers/relaxation.py</code> - Multi-phase relaxation strategy</li>
<li class=""><code>coordinator/periods.py</code> - Period calculator orchestration</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="core-filtering-criteria">Core Filtering Criteria<a href="#core-filtering-criteria" class="hash-link" aria-label="Direct link to Core Filtering Criteria" title="Direct link to Core Filtering Criteria" translate="no"></a></h2>
<p>Period detection uses <strong>three independent filters</strong> (all must pass):</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-flex-filter-price-distance-from-reference">1. Flex Filter (Price Distance from Reference)<a href="#1-flex-filter-price-distance-from-reference" class="hash-link" aria-label="Direct link to 1. Flex Filter (Price Distance from Reference)" title="Direct link to 1. Flex Filter (Price Distance from Reference)" translate="no"></a></h3>
<p><strong>Purpose:</strong> Limit how far prices can deviate from the daily min/max.</p>
<p><strong>Logic:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Best Price: Price must be within flex% ABOVE daily minimum</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">in_flex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> price </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">daily_min </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> daily_min × flex</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Peak Price: Price must be within flex% BELOW daily maximum</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">in_flex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> price </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">daily_max </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> daily_max × flex</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p><strong>Example (Best Price):</strong></p>
<ul>
<li class="">Daily Min: 10 ct/kWh</li>
<li class="">Flex: 15%</li>
<li class="">Acceptance Range: 0 - 11.5 ct/kWh (10 + 10×0.15)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-min-distance-filter-distance-from-daily-average">2. Min Distance Filter (Distance from Daily Average)<a href="#2-min-distance-filter-distance-from-daily-average" class="hash-link" aria-label="Direct link to 2. Min Distance Filter (Distance from Daily Average)" title="Direct link to 2. Min Distance Filter (Distance from Daily Average)" translate="no"></a></h3>
<p><strong>Purpose:</strong> Ensure periods are <strong>significantly</strong> cheaper/more expensive than average, not just marginally better.</p>
<p><strong>Logic:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Best Price: Price must be at least min_distance% BELOW daily average</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">meets_distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> price </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">daily_avg × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> min_distance</span><span class="token operator" style="color:#393A34">/</span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Peak Price: Price must be at least min_distance% ABOVE daily average</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">meets_distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> price </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">daily_avg × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> min_distance</span><span class="token operator" style="color:#393A34">/</span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p><strong>Example (Best Price):</strong></p>
<ul>
<li class="">Daily Avg: 15 ct/kWh</li>
<li class="">Min Distance: 5%</li>
<li class="">Acceptance Range: 0 - 14.25 ct/kWh (15 × 0.95)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-level-filter-price-level-classification">3. Level Filter (Price Level Classification)<a href="#3-level-filter-price-level-classification" class="hash-link" aria-label="Direct link to 3. Level Filter (Price Level Classification)" title="Direct link to 3. Level Filter (Price Level Classification)" translate="no"></a></h3>
<p><strong>Purpose:</strong> Restrict periods to specific price classifications (VERY_CHEAP, CHEAP, NORMAL, EXPENSIVE, VERY_EXPENSIVE).</p>
<p><strong>Logic:</strong> See <code>level_filtering.py</code> for gap tolerance details.</p>
<p><strong>Volatility Thresholds - Important Separation:</strong></p>
<p>The integration maintains <strong>two independent sets</strong> of volatility thresholds:</p>
<ol>
<li class="">
<p><strong>Sensor Thresholds</strong> (user-configurable via <code>CONF_VOLATILITY_*_THRESHOLD</code>)</p>
<ul>
<li class="">Purpose: Display classification in <code>sensor.tibber_home_volatility_*</code></li>
<li class="">Default: LOW &lt; 10%, MEDIUM &lt; 20%, HIGH ≥ 20%</li>
<li class="">User can adjust in config flow options</li>
<li class="">Affects: Sensor state/attributes only</li>
</ul>
</li>
<li class="">
<p><strong>Period Filter Thresholds</strong> (internal, fixed)</p>
<ul>
<li class="">Purpose: Level filter criteria when using <code>level=&quot;volatility_low&quot;</code> etc.</li>
<li class="">Source: <code>PRICE_LEVEL_THRESHOLDS</code> in <code>const.py</code></li>
<li class="">Values: Same as sensor defaults (LOW &lt; 10%, MEDIUM &lt; 20%, HIGH ≥ 20%)</li>
<li class="">User <strong>cannot</strong> adjust these</li>
<li class="">Affects: Period candidate selection</li>
</ul>
</li>
</ol>
<p><strong>Rationale for Separation:</strong></p>
<ul>
<li class=""><strong>Sensor thresholds</strong> = Display preference (&quot;I want to see LOW at 15% instead of 10%&quot;)</li>
<li class=""><strong>Period thresholds</strong> = Algorithm configuration (tested defaults, complex interactions)</li>
<li class="">Changing sensor display should not affect automation behavior</li>
<li class="">Prevents unexpected side effects when user adjusts sensor classification</li>
<li class="">Period calculation has many interacting filters (Flex, Distance, Level) - exposing all internals would be error-prone</li>
</ul>
<p><strong>Implementation:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Sensor classification uses user config</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">user_low_threshold </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> config_entry</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">options</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">CONF_VOLATILITY_LOW_THRESHOLD</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Period filter uses fixed constants</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">period_low_threshold </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> PRICE_LEVEL_THRESHOLDS</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">&quot;volatility_low&quot;</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Always 10%</span><br></span></code></pre></div></div>
<p><strong>Status:</strong> Intentional design decision (Nov 2025). No plans to expose period thresholds to users.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-flex--min_distance-conflict">The Flex × Min_Distance Conflict<a href="#the-flex--min_distance-conflict" class="hash-link" aria-label="Direct link to The Flex × Min_Distance Conflict" title="Direct link to The Flex × Min_Distance Conflict" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="problem-statement">Problem Statement<a href="#problem-statement" class="hash-link" aria-label="Direct link to Problem Statement" title="Direct link to Problem Statement" translate="no"></a></h3>
<p><strong>These two filters can conflict when Flex is high!</strong></p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-best-price-with-flex50-min_distance5">Scenario: Best Price with Flex=50%, Min_Distance=5%<a href="#scenario-best-price-with-flex50-min_distance5" class="hash-link" aria-label="Direct link to Scenario: Best Price with Flex=50%, Min_Distance=5%" title="Direct link to Scenario: Best Price with Flex=50%, Min_Distance=5%" translate="no"></a></h4>
<p><strong>Given:</strong></p>
<ul>
<li class="">Daily Min: 10 ct/kWh</li>
<li class="">Daily Avg: 15 ct/kWh</li>
<li class="">Daily Max: 20 ct/kWh</li>
</ul>
<p><strong>Flex Filter (50%):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Max accepted = 10 + (10 × 0.50) = 15 ct/kWh</span><br></span></code></pre></div></div>
<p><strong>Min Distance Filter (5%):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Max accepted = 15 × (1 - 0.05) = 14.25 ct/kWh</span><br></span></code></pre></div></div>
<p><strong>Conflict:</strong></p>
<ul>
<li class="">Interval at 14.8 ct/kWh:<!-- -->
<ul>
<li class="">✅ Flex: 14.8 ≤ 15 (PASS)</li>
<li class="">❌ Distance: 14.8 &gt; 14.25 (FAIL)</li>
<li class=""><strong>Result:</strong> Rejected by Min_Distance even though Flex allows it!</li>
</ul>
</li>
</ul>
<p><strong>The Issue:</strong> At high Flex values, Min_Distance becomes the dominant filter and blocks intervals that Flex would permit. This defeats the purpose of having high Flex.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mathematical-analysis">Mathematical Analysis<a href="#mathematical-analysis" class="hash-link" aria-label="Direct link to Mathematical Analysis" title="Direct link to Mathematical Analysis" translate="no"></a></h3>
<p><strong>Conflict condition for Best Price:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">daily_min × (1 + flex) &gt; daily_avg × (1 - min_distance/100)</span><br></span></code></pre></div></div>
<p><strong>Typical values:</strong></p>
<ul>
<li class="">Min = 10, Avg = 15, Min_Distance = 5%</li>
<li class="">Conflict occurs when: <code>10 × (1 + flex) &gt; 14.25</code></li>
<li class="">Simplify: <code>flex &gt; 0.425</code> (42.5%)</li>
</ul>
<p><strong>Below 42.5% Flex:</strong> Both filters contribute meaningfully.
<strong>Above 42.5% Flex:</strong> Min_Distance dominates and blocks intervals.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="solution-dynamic-min_distance-scaling">Solution: Dynamic Min_Distance Scaling<a href="#solution-dynamic-min_distance-scaling" class="hash-link" aria-label="Direct link to Solution: Dynamic Min_Distance Scaling" title="Direct link to Solution: Dynamic Min_Distance Scaling" translate="no"></a></h3>
<p><strong>Approach:</strong> Reduce Min_Distance proportionally as Flex increases.</p>
<p><strong>Formula:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> flex </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.20</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 20% threshold</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> flex_excess </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> flex </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.20</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> scale_factor </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.25</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.0</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flex_excess × </span><span class="token number" style="color:#36acaa">2.5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adjusted_min_distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> original_min_distance × scale_factor</span><br></span></code></pre></div></div>
<p><strong>Scaling Table (Original Min_Distance = 5%):</strong></p>
<table><thead><tr><th>Flex</th><th>Scale Factor</th><th>Adjusted Min_Distance</th><th>Rationale</th></tr></thead><tbody><tr><td>≤20%</td><td>1.00</td><td>5.0%</td><td>Standard - both filters relevant</td></tr><tr><td>25%</td><td>0.88</td><td>4.4%</td><td>Slight reduction</td></tr><tr><td>30%</td><td>0.75</td><td>3.75%</td><td>Moderate reduction</td></tr><tr><td>40%</td><td>0.50</td><td>2.5%</td><td>Strong reduction - Flex dominates</td></tr><tr><td>50%</td><td>0.25</td><td>1.25%</td><td>Minimal distance - Flex decides</td></tr></tbody></table>
<p><strong>Why stop at 25% of original?</strong></p>
<ul>
<li class="">Min_Distance ensures periods are <strong>significantly</strong> different from average</li>
<li class="">Even at 1.25%, prevents &quot;flat days&quot; (little price variation) from accepting every interval</li>
<li class="">Maintains semantic meaning: &quot;this is a meaningful best/peak price period&quot;</li>
</ul>
<p><strong>Implementation:</strong> See <code>level_filtering.py</code><code>check_interval_criteria()</code></p>
<p><strong>Code Extract:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># coordinator/period_handlers/level_filtering.py</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FLEX_SCALING_THRESHOLD </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.20</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 20% - start adjusting min_distance</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">SCALE_FACTOR_WARNING_THRESHOLD </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.8</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Log when reduction &gt; 20%</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check_interval_criteria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">price</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> criteria</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># ... flex check ...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Dynamic min_distance scaling</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adjusted_min_distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> criteria</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">min_distance_from_avg</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> flex_abs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">abs</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">criteria</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">flex</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> flex_abs </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> FLEX_SCALING_THRESHOLD</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> flex_excess </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> flex_abs </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.20</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># How much above 20%</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> scale_factor </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">max</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">0.25</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.0</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flex_excess × </span><span class="token number" style="color:#36acaa">2.5</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adjusted_min_distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> criteria</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">min_distance_from_avg × scale_factor</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> scale_factor </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> SCALE_FACTOR_WARNING_THRESHOLD</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> _LOGGER</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">debug</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">&quot;High flex %.1f%% detected: Reducing min_distance %.1f%% → %.1f%%&quot;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> flex_abs × </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> criteria</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">min_distance_from_avg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adjusted_min_distance</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Apply adjusted min_distance in distance check</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> meets_min_distance </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> price </span><span class="token operator" style="color:#393A34">&lt;=</span><span class="token plain"> avg_price × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> adjusted_min_distance</span><span class="token operator" style="color:#393A34">/</span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Best Price</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># OR</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> price </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> avg_price × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> adjusted_min_distance</span><span class="token operator" style="color:#393A34">/</span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Peak Price</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p><strong>Why Linear Scaling?</strong></p>
<ul>
<li class="">Simple and predictable</li>
<li class="">No abrupt behavior changes</li>
<li class="">Easy to reason about for users and developers</li>
<li class="">Alternative considered: Exponential scaling (rejected as too aggressive)</li>
</ul>
<p><strong>Why 25% Minimum?</strong></p>
<ul>
<li class="">Below this, min_distance loses semantic meaning</li>
<li class="">Even on flat days, some quality filter needed</li>
<li class="">Prevents &quot;every interval is a period&quot; scenario</li>
<li class="">Maintains user expectation: &quot;best/peak price means notably different&quot;</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flex-limits-and-safety-caps">Flex Limits and Safety Caps<a href="#flex-limits-and-safety-caps" class="hash-link" aria-label="Direct link to Flex Limits and Safety Caps" title="Direct link to Flex Limits and Safety Caps" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-constants">Implementation Constants<a href="#implementation-constants" class="hash-link" aria-label="Direct link to Implementation Constants" title="Direct link to Implementation Constants" translate="no"></a></h3>
<p><strong>Defined in <code>coordinator/period_handlers/core.py</code>:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">MAX_SAFE_FLEX </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.50</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 50% - hard cap: above this, period detection becomes unreliable</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">MAX_OUTLIER_FLEX </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 25% - cap for outlier filtering: above this, spike detection too permissive</span><br></span></code></pre></div></div>
<p><strong>Defined in <code>const.py</code>:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_BEST_PRICE_FLEX </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">15</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 15% base - optimal for relaxation mode (default enabled)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_PEAK_PRICE_FLEX </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">20</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 20% base (negative for peak detection)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_RELAXATION_ATTEMPTS_BEST </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">11</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 11 steps: 15% → 48% (3% increment per step)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_RELAXATION_ATTEMPTS_PEAK </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">11</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 11 steps: 20% → 50% (3% increment per step)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_BEST_PRICE_MIN_PERIOD_LENGTH </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">60</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 60 minutes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_PEAK_PRICE_MIN_PERIOD_LENGTH </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">30</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 30 minutes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_BEST_PRICE_MIN_DISTANCE_FROM_AVG </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 5% minimum distance</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEFAULT_PEAK_PRICE_MIN_DISTANCE_FROM_AVG </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 5% minimum distance</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="rationale-for-asymmetric-defaults">Rationale for Asymmetric Defaults<a href="#rationale-for-asymmetric-defaults" class="hash-link" aria-label="Direct link to Rationale for Asymmetric Defaults" title="Direct link to Rationale for Asymmetric Defaults" translate="no"></a></h3>
<p><strong>Why Best Price ≠ Peak Price?</strong></p>
<p>The different defaults reflect fundamentally different use cases:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="best-price-optimization-focus">Best Price: Optimization Focus<a href="#best-price-optimization-focus" class="hash-link" aria-label="Direct link to Best Price: Optimization Focus" title="Direct link to Best Price: Optimization Focus" translate="no"></a></h4>
<p><strong>Goal:</strong> Find practical time windows for running appliances</p>
<p><strong>Constraints:</strong></p>
<ul>
<li class="">Appliances need time to complete cycles (dishwasher: 2-3h, EV charging: 4-8h)</li>
<li class="">Short periods are impractical (not worth automation overhead)</li>
<li class="">User wants genuinely cheap times, not just &quot;slightly below average&quot;</li>
</ul>
<p><strong>Defaults:</strong></p>
<ul>
<li class=""><strong>60 min minimum</strong> - Ensures period is long enough for meaningful use</li>
<li class=""><strong>15% flex</strong> - Stricter selection, focuses on truly cheap times</li>
<li class=""><strong>Reasoning:</strong> Better to find fewer, higher-quality periods than many mediocre ones</li>
</ul>
<p><strong>User behavior:</strong></p>
<ul>
<li class="">Automations trigger actions (turn on devices)</li>
<li class="">Wrong automation = wasted energy/money</li>
<li class="">Preference: Conservative (miss some savings) over aggressive (false positives)</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="peak-price-warning-focus">Peak Price: Warning Focus<a href="#peak-price-warning-focus" class="hash-link" aria-label="Direct link to Peak Price: Warning Focus" title="Direct link to Peak Price: Warning Focus" translate="no"></a></h4>
<p><strong>Goal:</strong> Alert users to expensive periods for consumption reduction</p>
<p><strong>Constraints:</strong></p>
<ul>
<li class="">Brief price spikes still matter (even 15-30 min is worth avoiding)</li>
<li class="">Early warning more valuable than perfect accuracy</li>
<li class="">User can manually decide whether to react</li>
</ul>
<p><strong>Defaults:</strong></p>
<ul>
<li class=""><strong>30 min minimum</strong> - Catches shorter expensive spikes</li>
<li class=""><strong>20% flex</strong> - More permissive, earlier detection</li>
<li class=""><strong>Reasoning:</strong> Better to warn early (even if not peak) than miss expensive periods</li>
</ul>
<p><strong>User behavior:</strong></p>
<ul>
<li class="">Notifications/alerts (informational)</li>
<li class="">Wrong alert = minor inconvenience, not cost</li>
<li class="">Preference: Sensitive (catch more) over specific (catch only extremes)</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="mathematical-justification">Mathematical Justification<a href="#mathematical-justification" class="hash-link" aria-label="Direct link to Mathematical Justification" title="Direct link to Mathematical Justification" translate="no"></a></h4>
<p><strong>Peak Price Volatility:</strong></p>
<p>Price curves tend to have:</p>
<ul>
<li class=""><strong>Sharp spikes</strong> during peak hours (morning/evening)</li>
<li class=""><strong>Shorter duration</strong> at maximum (1-2 hours typical)</li>
<li class=""><strong>Higher variance</strong> in peak times than cheap times</li>
</ul>
<p><strong>Example day:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Cheap period: 02:00-07:00 (5 hours at 10-12 ct) ← Gradual, stable</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Expensive period: 17:00-18:30 (1.5 hours at 35-40 ct) ← Sharp, brief</span><br></span></code></pre></div></div>
<p><strong>Implication:</strong></p>
<ul>
<li class="">Stricter flex on peak (15%) might miss real expensive periods (too brief)</li>
<li class="">Longer min_length (60 min) might exclude legitimate spikes</li>
<li class="">Solution: More flexible thresholds for peak detection</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="design-alternatives-considered">Design Alternatives Considered<a href="#design-alternatives-considered" class="hash-link" aria-label="Direct link to Design Alternatives Considered" title="Direct link to Design Alternatives Considered" translate="no"></a></h4>
<p><strong>Option 1: Symmetric defaults (rejected)</strong></p>
<ul>
<li class="">Both 60 min, both 15% flex</li>
<li class="">Problem: Misses short but expensive spikes</li>
<li class="">User feedback: &quot;Why didn&#x27;t I get warned about the 30-min price spike?&quot;</li>
</ul>
<p><strong>Option 2: Same defaults, let users figure it out (rejected)</strong></p>
<ul>
<li class="">No guidance on best practices</li>
<li class="">Users would need to experiment to find good values</li>
<li class="">Most users stick with defaults, so defaults matter</li>
</ul>
<p><strong>Option 3: Current approach (adopted)</strong></p>
<ul>
<li class=""><strong>All values user-configurable</strong> via config flow options</li>
<li class=""><strong>Different installation defaults</strong> for Best Price vs. Peak Price</li>
<li class="">Defaults reflect recommended practices for each use case</li>
<li class="">Users who need different behavior can adjust</li>
<li class="">Most users benefit from sensible defaults without configuration</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="flex-limits-and-safety-caps-1">Flex Limits and Safety Caps<a href="#flex-limits-and-safety-caps-1" class="hash-link" aria-label="Direct link to Flex Limits and Safety Caps" title="Direct link to Flex Limits and Safety Caps" translate="no"></a></h2>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-absolute-maximum-50-max_safe_flex">1. Absolute Maximum: 50% (MAX_SAFE_FLEX)<a href="#1-absolute-maximum-50-max_safe_flex" class="hash-link" aria-label="Direct link to 1. Absolute Maximum: 50% (MAX_SAFE_FLEX)" title="Direct link to 1. Absolute Maximum: 50% (MAX_SAFE_FLEX)" translate="no"></a></h4>
<p><strong>Enforcement:</strong> <code>core.py</code> caps <code>abs(flex)</code> at 0.50 (50%)</p>
<p><strong>Rationale:</strong></p>
<ul>
<li class="">Above 50%, period detection becomes unreliable</li>
<li class="">Best Price: Almost entire day qualifies (Min + 50% typically covers 60-80% of intervals)</li>
<li class="">Peak Price: Similar issue with Max - 50%</li>
<li class=""><strong>Result:</strong> Either massive periods (entire day) or no periods (min_length not met)</li>
</ul>
<p><strong>Warning Message:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Flex XX% exceeds maximum safe value! Capping at 50%.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Recommendation: Use 15-20% with relaxation enabled, or 25-35% without relaxation.</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-outlier-filtering-maximum-25">2. Outlier Filtering Maximum: 25%<a href="#2-outlier-filtering-maximum-25" class="hash-link" aria-label="Direct link to 2. Outlier Filtering Maximum: 25%" title="Direct link to 2. Outlier Filtering Maximum: 25%" translate="no"></a></h4>
<p><strong>Enforcement:</strong> <code>core.py</code> caps outlier filtering flex at 0.25 (25%)</p>
<p><strong>Rationale:</strong></p>
<ul>
<li class="">Outlier filtering uses Flex to determine &quot;stable context&quot; threshold</li>
<li class="">At &gt; 25% Flex, almost any price swing is considered &quot;stable&quot;</li>
<li class=""><strong>Result:</strong> Legitimate price shifts aren&#x27;t smoothed, breaking period formation</li>
</ul>
<p><strong>Note:</strong> User&#x27;s Flex still applies to period criteria (<code>in_flex</code> check), only outlier filtering is capped.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="recommended-ranges-user-guidance">Recommended Ranges (User Guidance)<a href="#recommended-ranges-user-guidance" class="hash-link" aria-label="Direct link to Recommended Ranges (User Guidance)" title="Direct link to Recommended Ranges (User Guidance)" translate="no"></a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="with-relaxation-enabled-recommended">With Relaxation Enabled (Recommended)<a href="#with-relaxation-enabled-recommended" class="hash-link" aria-label="Direct link to With Relaxation Enabled (Recommended)" title="Direct link to With Relaxation Enabled (Recommended)" translate="no"></a></h4>
<p><strong>Optimal:</strong> 10-20%</p>
<ul>
<li class="">Relaxation increases Flex incrementally: 15% → 18% → 21% → ...</li>
<li class="">Low baseline ensures relaxation has room to work</li>
</ul>
<p><strong>Warning Threshold:</strong> &gt; 25%</p>
<ul>
<li class="">INFO log: &quot;Base flex is on the high side&quot;</li>
</ul>
<p><strong>High Warning:</strong> &gt; 30%</p>
<ul>
<li class="">WARNING log: &quot;Base flex is very high for relaxation mode!&quot;</li>
<li class="">Recommendation: Lower to 15-20%</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="without-relaxation">Without Relaxation<a href="#without-relaxation" class="hash-link" aria-label="Direct link to Without Relaxation" title="Direct link to Without Relaxation" translate="no"></a></h4>
<p><strong>Optimal:</strong> 20-35%</p>
<ul>
<li class="">No automatic adjustment, must be sufficient from start</li>
<li class="">Higher baseline acceptable since no relaxation fallback</li>
</ul>
<p><strong>Maximum Useful:</strong> ~50%</p>
<ul>
<li class="">Above this, period detection degrades (see Hard Limits)</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="relaxation-strategy">Relaxation Strategy<a href="#relaxation-strategy" class="hash-link" aria-label="Direct link to Relaxation Strategy" title="Direct link to Relaxation Strategy" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="purpose">Purpose<a href="#purpose" class="hash-link" aria-label="Direct link to Purpose" title="Direct link to Purpose" translate="no"></a></h3>
<p>Ensure <strong>minimum periods per day</strong> are found even when baseline filters are too strict.</p>
<p><strong>Use Case:</strong> User configures strict filters (low Flex, restrictive Level) but wants guarantee of N periods/day for automation reliability.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-phase-approach">Multi-Phase Approach<a href="#multi-phase-approach" class="hash-link" aria-label="Direct link to Multi-Phase Approach" title="Direct link to Multi-Phase Approach" translate="no"></a></h3>
<p><strong>Each day processed independently:</strong></p>
<ol>
<li class="">Calculate baseline periods with user&#x27;s config</li>
<li class="">If insufficient periods found, enter relaxation loop</li>
<li class="">Try progressively relaxed filter combinations</li>
<li class="">Stop when target reached or all attempts exhausted</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="relaxation-increments">Relaxation Increments<a href="#relaxation-increments" class="hash-link" aria-label="Direct link to Relaxation Increments" title="Direct link to Relaxation Increments" translate="no"></a></h3>
<p><strong>Current Implementation (November 2025):</strong></p>
<p><strong>File:</strong> <code>coordinator/period_handlers/relaxation.py</code></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Hard-coded 3% increment per step (reliability over configurability)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">flex_increment </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.03</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 3% per step</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">base_flex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">abs</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">flex</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Generate flex levels</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> attempt </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> </span><span class="token builtin">range</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">max_relaxation_attempts</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> flex_level </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> base_flex </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">attempt × flex_increment</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Try flex_level with both filter combinations</span><br></span></code></pre></div></div>
<p><strong>Constants:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">FLEX_WARNING_THRESHOLD_RELAXATION </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 25% - INFO: suggest lowering to 15-20%</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">FLEX_HIGH_THRESHOLD_RELAXATION </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.30</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 30% - WARNING: very high for relaxation mode</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">MAX_FLEX_HARD_LIMIT </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.50</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 50% - absolute maximum (enforced in core.py)</span><br></span></code></pre></div></div>
<p><strong>Design Decisions:</strong></p>
<ol>
<li class="">
<p><strong>Why 3% fixed increment?</strong></p>
<ul>
<li class="">Predictable escalation path (15% → 18% → 21% → ...)</li>
<li class="">Independent of base flex (works consistently)</li>
<li class="">11 attempts covers full useful range (15% → 48%)</li>
<li class="">Balance: Not too slow (2%), not too fast (5%)</li>
</ul>
</li>
<li class="">
<p><strong>Why hard-coded, not configurable?</strong></p>
<ul>
<li class="">Prevents user misconfiguration</li>
<li class="">Simplifies mental model (fewer knobs to turn)</li>
<li class="">Reliable behavior across all configurations</li>
<li class="">If needed, user adjusts <code>max_relaxation_attempts</code> (fewer/more steps)</li>
</ul>
</li>
<li class="">
<p><strong>Why warn at 25% base flex?</strong></p>
<ul>
<li class="">At 25% base, first relaxation step reaches 28%</li>
<li class="">Above 30%, entering diminishing returns territory</li>
<li class="">User likely doesn&#x27;t need relaxation with such high base flex</li>
<li class="">Should either: (a) lower base flex, or (b) disable relaxation</li>
</ul>
</li>
</ol>
<p><strong>Historical Context (Pre-November 2025):</strong></p>
<p>The algorithm previously used percentage-based increments that scaled with base flex:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">increment </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> base_flex × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">step_pct </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># REMOVED</span><br></span></code></pre></div></div>
<p>This caused exponential escalation with high base flex values (e.g., 40% → 50% → 60% → 70% in just 6 steps), making behavior unpredictable. The fixed 3% increment solves this by providing consistent, controlled escalation regardless of starting point.</p>
<p><strong>Warning Messages:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> base_flex </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> FLEX_HIGH_THRESHOLD_RELAXATION</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 30%</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> _LOGGER</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">warning</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">&quot;Base flex %.1f%% is very high for relaxation mode! &quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">&quot;Consider lowering to 15-20%% or disabling relaxation.&quot;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> base_flex × </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">elif</span><span class="token plain"> base_flex </span><span class="token operator" style="color:#393A34">&gt;=</span><span class="token plain"> FLEX_WARNING_THRESHOLD_RELAXATION</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 25%</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> _LOGGER</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">info</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">&quot;Base flex %.1f%% is on the high side. &quot;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token string" style="color:#e3116c">&quot;Consider 15-20%% for optimal relaxation effectiveness.&quot;</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> base_flex × </span><span class="token number" style="color:#36acaa">100</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="filter-combination-strategy">Filter Combination Strategy<a href="#filter-combination-strategy" class="hash-link" aria-label="Direct link to Filter Combination Strategy" title="Direct link to Filter Combination Strategy" translate="no"></a></h3>
<p><strong>Per Flex level, try in order:</strong></p>
<ol>
<li class="">Original Level filter</li>
<li class="">Level filter = &quot;any&quot; (disabled)</li>
</ol>
<p><strong>Early Exit:</strong> Stop immediately when target reached (don&#x27;t try unnecessary combinations)</p>
<p><strong>Example Flow (target=2 periods/day):</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Day 2025-11-19:</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">1. Baseline flex=15%: Found 1 period (need 2)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">2. Flex=18% + level=cheap: Found 1 period</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">3. Flex=18% + level=any: Found 2 periods → SUCCESS (stop)</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-notes">Implementation Notes<a href="#implementation-notes" class="hash-link" aria-label="Direct link to Implementation Notes" title="Direct link to Implementation Notes" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-files-and-functions">Key Files and Functions<a href="#key-files-and-functions" class="hash-link" aria-label="Direct link to Key Files and Functions" title="Direct link to Key Files and Functions" translate="no"></a></h3>
<p><strong>Period Calculation Entry Point:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># coordinator/period_handlers/core.py</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calculate_periods</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> all_prices</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">list</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PeriodConfig</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> TimeService</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">str</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> Any</span><span class="token punctuation" style="color:#393A34">]</span><br></span></code></pre></div></div>
<p><strong>Flex + Distance Filtering:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># coordinator/period_handlers/level_filtering.py</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">check_interval_criteria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> price</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token builtin">float</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> criteria</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> IntervalCriteria</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">tuple</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">bool</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">bool</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># (in_flex, meets_min_distance)</span><br></span></code></pre></div></div>
<p><strong>Relaxation Orchestration:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># coordinator/period_handlers/relaxation.py</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">calculate_periods_with_relaxation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">tuple</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">def</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">relax_single_day</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token builtin">tuple</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">dict</span><span class="token punctuation" style="color:#393A34">]</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="outlier-filtering-implementation">Outlier Filtering Implementation<a href="#outlier-filtering-implementation" class="hash-link" aria-label="Direct link to Outlier Filtering Implementation" title="Direct link to Outlier Filtering Implementation" translate="no"></a></h4>
<p><strong>File:</strong> <code>coordinator/period_handlers/outlier_filtering.py</code></p>
<p><strong>Purpose:</strong> Detect and smooth isolated price spikes before period identification to prevent artificial fragmentation.</p>
<p><strong>Algorithm Details:</strong></p>
<ol>
<li class="">
<p><strong>Linear Regression Prediction:</strong></p>
<ul>
<li class="">Uses surrounding intervals to predict expected price</li>
<li class="">Window size: 3+ intervals (MIN_CONTEXT_SIZE)</li>
<li class="">Calculates trend slope and standard deviation</li>
<li class="">Formula: <code>predicted = mean + slope × (position - center)</code></li>
</ul>
</li>
<li class="">
<p><strong>Confidence Intervals:</strong></p>
<ul>
<li class="">95% confidence level (2 standard deviations)</li>
<li class="">Tolerance = 2.0 × std_dev (CONFIDENCE_LEVEL constant)</li>
<li class="">Outlier if: <code>|actual - predicted| &gt; tolerance</code></li>
<li class="">Accounts for natural price volatility in context window</li>
</ul>
</li>
<li class="">
<p><strong>Symmetry Check:</strong></p>
<ul>
<li class="">Rejects asymmetric outliers (threshold: 1.5 std dev)</li>
<li class="">Preserves legitimate price shifts (morning/evening peaks)</li>
<li class="">Algorithm:<!-- -->
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">residual </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token builtin">abs</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">actual </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> predicted</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">symmetry_threshold </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.5</span><span class="token plain"> × std_dev</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> residual </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> tolerance</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Check if spike is symmetric in context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> context_residuals </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">abs</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">p </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> pred</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> p</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> pred </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> context</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> avg_context_residual </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> mean</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context_residuals</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> residual </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> symmetry_threshold × avg_context_residual</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Asymmetric spike → smooth it</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">else</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Symmetric (part of trend) → keep it</span><br></span></code></pre></div></div>
</li>
</ul>
</li>
<li class="">
<p><strong>Enhanced Zigzag Detection:</strong></p>
<ul>
<li class="">Detects spike clusters via relative volatility</li>
<li class="">Threshold: 2.0× local volatility (RELATIVE_VOLATILITY_THRESHOLD)</li>
<li class="">Single-pass algorithm (no iteration needed)</li>
<li class="">Catches patterns like: 18, 35, 19, 34, 18 (alternating spikes)</li>
</ul>
</li>
</ol>
<p><strong>Constants:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># coordinator/period_handlers/outlier_filtering.py</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">CONFIDENCE_LEVEL </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># 95% confidence (2 std deviations)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">SYMMETRY_THRESHOLD </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1.5</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Asymmetry detection threshold</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">RELATIVE_VOLATILITY_THRESHOLD </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Zigzag spike detection</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">MIN_CONTEXT_SIZE </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">3</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Minimum intervals for regression</span><br></span></code></pre></div></div>
<p><strong>Data Integrity:</strong></p>
<ul>
<li class="">Original prices stored in <code>_original_price</code> field</li>
<li class="">All statistics (daily min/max/avg) use original prices</li>
<li class="">Smoothing only affects period formation logic</li>
<li class="">Smart counting: Only counts smoothing that changed period outcome</li>
</ul>
<p><strong>Performance:</strong></p>
<ul>
<li class="">Single pass through price data</li>
<li class="">O(n) complexity with small context window</li>
<li class="">No iterative refinement needed</li>
<li class="">Typical processing time: <code>&lt;</code>1ms for 96 intervals</li>
</ul>
<p><strong>Example Debug Output:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: [2025-11-11T14:30:00+01:00] Outlier detected: 35.2 ct</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Context: 18.5, 19.1, 19.3, 19.8, 20.2 ct</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Residual: 14.5 ct &gt; tolerance: 4.8 ct (2×2.4 std dev)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Trend slope: 0.3 ct/interval (gradual increase)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Predicted: 20.7 ct (linear regression)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Smoothed to: 20.7 ct</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Asymmetry ratio: 3.2 (&gt;1.5 threshold) → confirmed outlier</span><br></span></code></pre></div></div>
<p><strong>Why This Approach?</strong></p>
<ol>
<li class="">
<p><strong>Linear regression over moving average:</strong></p>
<ul>
<li class="">Accounts for price trends (morning ramp-up, evening decline)</li>
<li class="">Moving average can&#x27;t predict direction, only level</li>
<li class="">Better accuracy on non-stationary price curves</li>
</ul>
</li>
<li class="">
<p><strong>Symmetry check over fixed threshold:</strong></p>
<ul>
<li class="">Prevents false positives on legitimate price shifts</li>
<li class="">Adapts to local volatility patterns</li>
<li class="">Preserves user expectation: &quot;expensive during peak hours&quot;</li>
</ul>
</li>
<li class="">
<p><strong>Single-pass over iterative:</strong></p>
<ul>
<li class="">Predictable behavior (no convergence issues)</li>
<li class="">Fast and deterministic</li>
<li class="">Easier to debug and reason about</li>
</ul>
</li>
</ol>
<p><strong>Alternative Approaches Considered:</strong></p>
<ol>
<li class=""><strong>Median filtering</strong> - Rejected: Too aggressive, removes legitimate peaks</li>
<li class=""><strong>Moving average</strong> - Rejected: Can&#x27;t handle trends</li>
<li class=""><strong>IQR (Interquartile Range)</strong> - Rejected: Assumes normal distribution</li>
<li class=""><strong>RANSAC</strong> - Rejected: Overkill for 1D data, slow</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="debugging-tips">Debugging Tips<a href="#debugging-tips" class="hash-link" aria-label="Direct link to Debugging Tips" title="Direct link to Debugging Tips" translate="no"></a></h2>
<p><strong>Enable DEBUG logging:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># configuration.yaml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">logger</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">default</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> info</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">logs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">custom_components.tibber_prices.coordinator.period_handlers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> debug</span><br></span></code></pre></div></div>
<p><strong>Key log messages to watch:</strong></p>
<ol>
<li class=""><code>&quot;Filter statistics: X intervals checked&quot;</code> - Shows how many intervals filtered by each criterion</li>
<li class=""><code>&quot;After build_periods: X raw periods found&quot;</code> - Periods before min_length filtering</li>
<li class=""><code>&quot;Day X: Success with flex=Y%&quot;</code> - Relaxation succeeded</li>
<li class=""><code>&quot;High flex X% detected: Reducing min_distance Y% → Z%&quot;</code> - Distance scaling active</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-configuration-pitfalls">Common Configuration Pitfalls<a href="#common-configuration-pitfalls" class="hash-link" aria-label="Direct link to Common Configuration Pitfalls" title="Direct link to Common Configuration Pitfalls" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-anti-pattern-1-high-flex-with-relaxation">❌ Anti-Pattern 1: High Flex with Relaxation<a href="#-anti-pattern-1-high-flex-with-relaxation" class="hash-link" aria-label="Direct link to ❌ Anti-Pattern 1: High Flex with Relaxation" title="Direct link to ❌ Anti-Pattern 1: High Flex with Relaxation" translate="no"></a></h3>
<p><strong>Configuration:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">best_price_flex</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">40</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">enable_relaxation_best</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><br></span></code></pre></div></div>
<p><strong>Problem:</strong></p>
<ul>
<li class="">Base Flex 40% already very permissive</li>
<li class="">Relaxation increments further (43%, 46%, 49%, ...)</li>
<li class="">Quickly approaches 50% cap with diminishing returns</li>
</ul>
<p><strong>Solution:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">best_price_flex</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">15</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Let relaxation increase it</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">enable_relaxation_best</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-anti-pattern-2-zero-min_distance">❌ Anti-Pattern 2: Zero Min_Distance<a href="#-anti-pattern-2-zero-min_distance" class="hash-link" aria-label="Direct link to ❌ Anti-Pattern 2: Zero Min_Distance" title="Direct link to ❌ Anti-Pattern 2: Zero Min_Distance" translate="no"></a></h3>
<p><strong>Configuration:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">best_price_min_distance_from_avg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><br></span></code></pre></div></div>
<p><strong>Problem:</strong></p>
<ul>
<li class="">&quot;Flat days&quot; (little price variation) accept all intervals</li>
<li class="">Periods lose semantic meaning (&quot;significantly cheap&quot;)</li>
<li class="">May create periods during barely-below-average times</li>
</ul>
<p><strong>Solution:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">best_price_min_distance_from_avg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Use default 5%</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-anti-pattern-3-conflicting-flex--distance">❌ Anti-Pattern 3: Conflicting Flex + Distance<a href="#-anti-pattern-3-conflicting-flex--distance" class="hash-link" aria-label="Direct link to ❌ Anti-Pattern 3: Conflicting Flex + Distance" title="Direct link to ❌ Anti-Pattern 3: Conflicting Flex + Distance" translate="no"></a></h3>
<p><strong>Configuration:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">best_price_flex</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">45</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">best_price_min_distance_from_avg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><br></span></code></pre></div></div>
<p><strong>Problem:</strong></p>
<ul>
<li class="">Distance filter dominates, making Flex irrelevant</li>
<li class="">Dynamic scaling helps but still suboptimal</li>
</ul>
<p><strong>Solution:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">best_price_flex</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">20</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">best_price_min_distance_from_avg</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-scenarios">Testing Scenarios<a href="#testing-scenarios" class="hash-link" aria-label="Direct link to Testing Scenarios" title="Direct link to Testing Scenarios" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-1-normal-day-good-variation">Scenario 1: Normal Day (Good Variation)<a href="#scenario-1-normal-day-good-variation" class="hash-link" aria-label="Direct link to Scenario 1: Normal Day (Good Variation)" title="Direct link to Scenario 1: Normal Day (Good Variation)" translate="no"></a></h3>
<p><strong>Price Range:</strong> 10 - 20 ct/kWh (100% variation)
<strong>Average:</strong> 15 ct/kWh</p>
<p><strong>Expected Behavior:</strong></p>
<ul>
<li class="">Flex 15%: Should find 2-4 clear best price periods</li>
<li class="">Flex 30%: Should find 4-8 periods (more lenient)</li>
<li class="">Min_Distance 5%: Effective throughout range</li>
</ul>
<p><strong>Debug Checks:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filter statistics: 96 intervals checked</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filtered by FLEX: 12/96 (12.5%) ← Low percentage = good variation</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filtered by MIN_DISTANCE: 8/96 (8.3%) ← Both filters active</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: After build_periods: 3 raw periods found</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-2-flat-day-poor-variation">Scenario 2: Flat Day (Poor Variation)<a href="#scenario-2-flat-day-poor-variation" class="hash-link" aria-label="Direct link to Scenario 2: Flat Day (Poor Variation)" title="Direct link to Scenario 2: Flat Day (Poor Variation)" translate="no"></a></h3>
<p><strong>Price Range:</strong> 14 - 16 ct/kWh (14% variation)
<strong>Average:</strong> 15 ct/kWh</p>
<p><strong>Expected Behavior:</strong></p>
<ul>
<li class="">Flex 15%: May find 1-2 small periods (or zero if no clear winners)</li>
<li class="">Min_Distance 5%: Critical here - ensures only truly cheaper intervals qualify</li>
<li class="">Without Min_Distance: Would accept almost entire day as &quot;best price&quot;</li>
</ul>
<p><strong>Debug Checks:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filter statistics: 96 intervals checked</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filtered by FLEX: 45/96 (46.9%) ← High percentage = poor variation</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filtered by MIN_DISTANCE: 52/96 (54.2%) ← Distance filter dominant</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: After build_periods: 1 raw period found</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Day 2025-11-11: Baseline insufficient (1 &lt; 2), starting relaxation</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-3-extreme-day-high-volatility">Scenario 3: Extreme Day (High Volatility)<a href="#scenario-3-extreme-day-high-volatility" class="hash-link" aria-label="Direct link to Scenario 3: Extreme Day (High Volatility)" title="Direct link to Scenario 3: Extreme Day (High Volatility)" translate="no"></a></h3>
<p><strong>Price Range:</strong> 5 - 40 ct/kWh (700% variation)
<strong>Average:</strong> 18 ct/kWh</p>
<p><strong>Expected Behavior:</strong></p>
<ul>
<li class="">Flex 15%: Finds multiple very cheap periods (5-6 ct)</li>
<li class="">Outlier filtering: May smooth isolated spikes (30-40 ct)</li>
<li class="">Distance filter: Less impactful (clear separation between cheap/expensive)</li>
</ul>
<p><strong>Debug Checks:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Outlier detected: 38.5 ct (threshold: 4.2 ct)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Smoothed to: 20.1 ct (trend prediction)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filter statistics: 96 intervals checked</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filtered by FLEX: 8/96 (8.3%) ← Very selective</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Filtered by MIN_DISTANCE: 4/96 (4.2%) ← Flex dominates</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: After build_periods: 4 raw periods found</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-4-relaxation-success">Scenario 4: Relaxation Success<a href="#scenario-4-relaxation-success" class="hash-link" aria-label="Direct link to Scenario 4: Relaxation Success" title="Direct link to Scenario 4: Relaxation Success" translate="no"></a></h3>
<p><strong>Initial State:</strong> Baseline finds 1 period, target is 2</p>
<p><strong>Expected Flow:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">INFO: Calculating BEST PRICE periods: relaxation=ON, target=2/day, flex=15.0%</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Day 2025-11-11: Baseline found 1 period (need 2)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Phase 1: flex 18.0% + original filters</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Found 1 period (insufficient)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Phase 2: flex 18.0% + level=any</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Found 2 periods → SUCCESS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">INFO: Day 2025-11-11: Success after 1 relaxation phase (2 periods)</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scenario-5-relaxation-exhausted">Scenario 5: Relaxation Exhausted<a href="#scenario-5-relaxation-exhausted" class="hash-link" aria-label="Direct link to Scenario 5: Relaxation Exhausted" title="Direct link to Scenario 5: Relaxation Exhausted" translate="no"></a></h3>
<p><strong>Initial State:</strong> Strict filters, very flat day</p>
<p><strong>Expected Flow:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">INFO: Calculating BEST PRICE periods: relaxation=ON, target=2/day, flex=15.0%</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Day 2025-11-11: Baseline found 0 periods (need 2)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DEBUG: Phase 1-11: flex 15%→48%, all filter combinations tried</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">WARNING: Day 2025-11-11: All relaxation phases exhausted, still only 1 period found</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">INFO: Period calculation completed: 1/2 days reached target</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="debugging-checklist">Debugging Checklist<a href="#debugging-checklist" class="hash-link" aria-label="Direct link to Debugging Checklist" title="Direct link to Debugging Checklist" translate="no"></a></h3>
<p>When debugging period calculation issues:</p>
<ol>
<li class="">
<p><strong>Check Filter Statistics</strong></p>
<ul>
<li class="">Which filter blocks most intervals? (flex, distance, or level)</li>
<li class="">High flex filtering (&gt;30%) = Need more flexibility or relaxation</li>
<li class="">High distance filtering (&gt;50%) = Min_distance too strict or flat day</li>
<li class="">High level filtering = Level filter too restrictive</li>
</ul>
</li>
<li class="">
<p><strong>Check Relaxation Behavior</strong></p>
<ul>
<li class="">Did relaxation activate? Check for &quot;Baseline insufficient&quot; message</li>
<li class="">Which phase succeeded? Early success (phase 1-3) = good config</li>
<li class="">Late success (phase 8-11) = Consider adjusting base config</li>
<li class="">Exhausted all phases = Unrealistic target for this day&#x27;s price curve</li>
</ul>
</li>
<li class="">
<p><strong>Check Flex Warnings</strong></p>
<ul>
<li class="">INFO at 25% base flex = On the high side</li>
<li class="">WARNING at 30% base flex = Too high for relaxation</li>
<li class="">If seeing these: Lower base flex to 15-20%</li>
</ul>
</li>
<li class="">
<p><strong>Check Min_Distance Scaling</strong></p>
<ul>
<li class="">Debug messages show &quot;High flex X% detected: Reducing min_distance Y% → Z%&quot;</li>
<li class="">If scale factor <code>&lt;</code>0.8 (20% reduction): High flex is active</li>
<li class="">If periods still not found: Filters conflict even with scaling</li>
</ul>
</li>
<li class="">
<p><strong>Check Outlier Filtering</strong></p>
<ul>
<li class="">Look for &quot;Outlier detected&quot; messages</li>
<li class="">Check <code>period_interval_smoothed_count</code> attribute</li>
<li class="">If no smoothing but periods fragmented: Not isolated spikes, but legitimate price levels</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="future-enhancements">Future Enhancements<a href="#future-enhancements" class="hash-link" aria-label="Direct link to Future Enhancements" title="Direct link to Future Enhancements" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="potential-improvements">Potential Improvements<a href="#potential-improvements" class="hash-link" aria-label="Direct link to Potential Improvements" title="Direct link to Potential Improvements" translate="no"></a></h3>
<ol>
<li class="">
<p><strong>Adaptive Flex Calculation:</strong></p>
<ul>
<li class="">Auto-adjust Flex based on daily price variation</li>
<li class="">High variation days: Lower Flex needed</li>
<li class="">Low variation days: Higher Flex needed</li>
</ul>
</li>
<li class="">
<p><strong>Machine Learning Approach:</strong></p>
<ul>
<li class="">Learn optimal Flex/Distance from user feedback</li>
<li class="">Classify days by pattern (normal/flat/volatile/bimodal)</li>
<li class="">Apply pattern-specific defaults</li>
</ul>
</li>
<li class="">
<p><strong>Multi-Objective Optimization:</strong></p>
<ul>
<li class="">Balance period count vs. quality</li>
<li class="">Consider period duration vs. price level</li>
<li class="">Optimize for user&#x27;s stated use case (EV charging vs. heat pump)</li>
</ul>
</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="known-limitations">Known Limitations<a href="#known-limitations" class="hash-link" aria-label="Direct link to Known Limitations" title="Direct link to Known Limitations" translate="no"></a></h3>
<ol>
<li class=""><strong>Fixed increment step:</strong> 3% cap may be too aggressive for very low base Flex</li>
<li class=""><strong>Linear distance scaling:</strong> Could benefit from non-linear curve</li>
<li class=""><strong>No consideration of temporal distribution:</strong> May find all periods in one part of day</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="future-enhancements-1">Future Enhancements<a href="#future-enhancements-1" class="hash-link" aria-label="Direct link to Future Enhancements" title="Direct link to Future Enhancements" translate="no"></a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="potential-improvements-1">Potential Improvements<a href="#potential-improvements-1" class="hash-link" aria-label="Direct link to Potential Improvements" title="Direct link to Potential Improvements" translate="no"></a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-adaptive-flex-calculation-not-yet-implemented">1. Adaptive Flex Calculation (Not Yet Implemented)<a href="#1-adaptive-flex-calculation-not-yet-implemented" class="hash-link" aria-label="Direct link to 1. Adaptive Flex Calculation (Not Yet Implemented)" title="Direct link to 1. Adaptive Flex Calculation (Not Yet Implemented)" translate="no"></a></h4>
<p><strong>Concept:</strong> Auto-adjust Flex based on daily price variation</p>
<p><strong>Algorithm:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Pseudo-code for adaptive flex</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">variation </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">daily_max </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> daily_min</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> daily_avg</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> variation </span><span class="token operator" style="color:#393A34">&lt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Flat day (&lt; 15% variation)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adaptive_flex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.30</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Need higher flex</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">elif</span><span class="token plain"> variation </span><span class="token operator" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.50</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># High volatility (&gt; 50% variation)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adaptive_flex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.10</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Lower flex sufficient</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">else</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Normal day</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> adaptive_flex </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.15</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Standard flex</span><br></span></code></pre></div></div>
<p><strong>Benefits:</strong></p>
<ul>
<li class="">Eliminates need for relaxation on most days</li>
<li class="">Self-adjusting to market conditions</li>
<li class="">Better user experience (less configuration needed)</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li class="">Harder to predict behavior (less transparent)</li>
<li class="">May conflict with user&#x27;s mental model</li>
<li class="">Needs extensive testing across different markets</li>
</ul>
<p><strong>Status:</strong> Considered but not implemented (prefer explicit relaxation)</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-machine-learning-approach-future-work">2. Machine Learning Approach (Future Work)<a href="#2-machine-learning-approach-future-work" class="hash-link" aria-label="Direct link to 2. Machine Learning Approach (Future Work)" title="Direct link to 2. Machine Learning Approach (Future Work)" translate="no"></a></h4>
<p><strong>Concept:</strong> Learn optimal Flex/Distance from user feedback</p>
<p><strong>Approach:</strong></p>
<ul>
<li class="">Track which periods user actually uses (automation triggers)</li>
<li class="">Classify days by pattern (normal/flat/volatile/bimodal)</li>
<li class="">Apply pattern-specific defaults</li>
<li class="">Learn per-user preferences over time</li>
</ul>
<p><strong>Benefits:</strong></p>
<ul>
<li class="">Personalized to user&#x27;s actual behavior</li>
<li class="">Adapts to local market patterns</li>
<li class="">Could discover non-obvious patterns</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li class="">Requires user feedback mechanism (not implemented)</li>
<li class="">Privacy concerns (storing usage patterns)</li>
<li class="">Complexity for users to understand &quot;why this period?&quot;</li>
<li class="">Cold start problem (new users have no history)</li>
</ul>
<p><strong>Status:</strong> Theoretical only (no implementation planned)</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-multi-objective-optimization-research-idea">3. Multi-Objective Optimization (Research Idea)<a href="#3-multi-objective-optimization-research-idea" class="hash-link" aria-label="Direct link to 3. Multi-Objective Optimization (Research Idea)" title="Direct link to 3. Multi-Objective Optimization (Research Idea)" translate="no"></a></h4>
<p><strong>Concept:</strong> Balance multiple goals simultaneously</p>
<p><strong>Goals:</strong></p>
<ul>
<li class="">Period count vs. quality (cheap vs. very cheap)</li>
<li class="">Period duration vs. price level (long mediocre vs. short excellent)</li>
<li class="">Temporal distribution (spread throughout day vs. clustered)</li>
<li class="">User&#x27;s stated use case (EV charging vs. heat pump vs. dishwasher)</li>
</ul>
<p><strong>Algorithm:</strong></p>
<ul>
<li class="">Pareto optimization (find trade-off frontier)</li>
<li class="">User chooses point on frontier via preferences</li>
<li class="">Genetic algorithm or simulated annealing</li>
</ul>
<p><strong>Benefits:</strong></p>
<ul>
<li class="">More sophisticated period selection</li>
<li class="">Better match to user&#x27;s actual needs</li>
<li class="">Could handle complex appliance requirements</li>
</ul>
<p><strong>Challenges:</strong></p>
<ul>
<li class="">Much more complex to implement</li>
<li class="">Harder to explain to users</li>
<li class="">Computational cost (may need caching)</li>
<li class="">Configuration explosion (too many knobs)</li>
</ul>
<p><strong>Status:</strong> Research idea only (not planned)</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="known-limitations-1">Known Limitations<a href="#known-limitations-1" class="hash-link" aria-label="Direct link to Known Limitations" title="Direct link to Known Limitations" translate="no"></a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-fixed-increment-step">1. Fixed Increment Step<a href="#1-fixed-increment-step" class="hash-link" aria-label="Direct link to 1. Fixed Increment Step" title="Direct link to 1. Fixed Increment Step" translate="no"></a></h4>
<p><strong>Current:</strong> 3% cap may be too aggressive for very low base Flex</p>
<p><strong>Example:</strong></p>
<ul>
<li class="">Base flex 5% + 3% increment = 8% (60% increase!)</li>
<li class="">Base flex 15% + 3% increment = 18% (20% increase)</li>
</ul>
<p><strong>Possible Solution:</strong></p>
<ul>
<li class="">Percentage-based increment: <code>increment = max(base_flex × 0.20, 0.03)</code></li>
<li class="">This gives: 5% → 6% (20%), 15% → 18% (20%), 40% → 43% (7.5%)</li>
</ul>
<p><strong>Why Not Implemented:</strong></p>
<ul>
<li class="">Very low base flex (<code>&lt;</code>10%) unusual</li>
<li class="">Users with strict requirements likely disable relaxation</li>
<li class="">Simplicity preferred over edge case optimization</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-linear-distance-scaling">2. Linear Distance Scaling<a href="#2-linear-distance-scaling" class="hash-link" aria-label="Direct link to 2. Linear Distance Scaling" title="Direct link to 2. Linear Distance Scaling" translate="no"></a></h4>
<p><strong>Current:</strong> Linear scaling may be too aggressive/conservative</p>
<p><strong>Alternative:</strong> Non-linear curve</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># Example: Exponential scaling</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">scale_factor </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.75</span><span class="token plain"> × exp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">5</span><span class="token plain"> × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flex </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.20</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Or: Sigmoid scaling</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">scale_factor </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.25</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.75</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">/</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">1</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">+</span><span class="token plain"> exp</span><span class="token punctuation" style="color:#393A34">(</span><span class="token number" style="color:#36acaa">10</span><span class="token plain"> × </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">flex </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.35</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p><strong>Why Not Implemented:</strong></p>
<ul>
<li class="">Linear is easier to reason about</li>
<li class="">No evidence that non-linear is better</li>
<li class="">Would need extensive testing</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-no-temporal-distribution-consideration">3. No Temporal Distribution Consideration<a href="#3-no-temporal-distribution-consideration" class="hash-link" aria-label="Direct link to 3. No Temporal Distribution Consideration" title="Direct link to 3. No Temporal Distribution Consideration" translate="no"></a></h4>
<p><strong>Issue:</strong> May find all periods in one part of day</p>
<p><strong>Example:</strong></p>
<ul>
<li class="">All 3 &quot;best price&quot; periods between 02:00-08:00</li>
<li class="">No periods in evening (when user might want to run appliances)</li>
</ul>
<p><strong>Possible Solution:</strong></p>
<ul>
<li class="">Add &quot;spread&quot; parameter (prefer distributed periods)</li>
<li class="">Weight periods by time-of-day preferences</li>
<li class="">Consider user&#x27;s typical usage patterns</li>
</ul>
<p><strong>Why Not Implemented:</strong></p>
<ul>
<li class="">Adds complexity</li>
<li class="">Users can work around with multiple automations</li>
<li class="">Different users have different needs (no one-size-fits-all)</li>
</ul>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-period-boundary-handling">4. Period Boundary Handling<a href="#4-period-boundary-handling" class="hash-link" aria-label="Direct link to 4. Period Boundary Handling" title="Direct link to 4. Period Boundary Handling" translate="no"></a></h4>
<p><strong>Current Behavior:</strong> Periods can cross midnight naturally</p>
<p><strong>Design Principle:</strong> Each interval is evaluated using its <strong>own day&#x27;s</strong> reference prices (daily min/max/avg).</p>
<p><strong>Implementation:</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># In period_building.py build_periods():</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">for</span><span class="token plain"> price_data </span><span class="token keyword" style="color:#00009f">in</span><span class="token plain"> all_prices</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> starts_at </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">get_interval_time</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">price_data</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> date_key </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> starts_at</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">date</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># CRITICAL: Use interval&#x27;s own day, not period_start_date</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ref_date </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> date_key</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> criteria </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> TibberPricesIntervalCriteria</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ref_price</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">ref_prices</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ref_date</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Interval&#x27;s day</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> avg_price</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">avg_prices</span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain">ref_date</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Interval&#x27;s day</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> flex</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">flex</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> min_distance_from_avg</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">min_distance_from_avg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> reverse_sort</span><span class="token operator" style="color:#393A34">=</span><span class="token plain">reverse_sort</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre></div></div>
<p><strong>Why Per-Day Evaluation?</strong></p>
<p>Periods can cross midnight (e.g., 23:45 → 01:00). Each day has independent reference prices calculated from its 96 intervals.</p>
<p><strong>Example showing the problem with period-start-day approach:</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Day 1 (2025-11-21): Cheap day</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> daily_min = 10 ct, daily_avg = 20 ct, flex = 15%</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Criteria: price ≤ 11.5 ct (10 + 10×0.15)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Day 2 (2025-11-22): Expensive day</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> daily_min = 20 ct, daily_avg = 30 ct, flex = 15%</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Criteria: price ≤ 23 ct (20 + 20×0.15)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Period crossing midnight: 23:45 Day 1 → 00:15 Day 2</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 23:45 (Day 1): 11 ct → ✅ Passes (11 ≤ 11.5)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 00:00 (Day 2): 21 ct → Should this pass?</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">❌ WRONG (using period start day):</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 00:00 evaluated against Day 1&#x27;s 11.5 ct threshold</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 21 ct &gt; 11.5 ct → Fails</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> But 21ct IS cheap on Day 2 (min=20ct)!</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">✅ CORRECT (using interval&#x27;s own day):</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 00:00 evaluated against Day 2&#x27;s 23 ct threshold</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 21 ct ≤ 23 ct → Passes</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Correctly identified as cheap relative to Day 2</span><br></span></code></pre></div></div>
<p><strong>Trade-off: Periods May Break at Midnight</strong></p>
<p>When days differ significantly, period can split:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">Day 1: Min=10ct, Avg=20ct, 23:45=11ct → ✅ Cheap (relative to Day 1)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Day 2: Min=25ct, Avg=35ct, 00:00=21ct → ❌ Expensive (relative to Day 2)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">Result: Period stops at 23:45, new period starts later</span><br></span></code></pre></div></div>
<p>This is <strong>mathematically correct</strong> - 21ct is genuinely expensive on a day where minimum is 25ct.</p>
<p><strong>Market Reality Explains Price Jumps:</strong></p>
<p>Day-ahead electricity markets (EPEX SPOT) set prices at 12:00 CET for all next-day hours:</p>
<ul>
<li class="">Late intervals (23:45): Priced ~36h before delivery → high forecast uncertainty → risk premium</li>
<li class="">Early intervals (00:00): Priced ~12h before delivery → better forecasts → lower risk buffer</li>
</ul>
<p>This explains why absolute prices jump at midnight despite minimal demand changes.</p>
<p><strong>User-Facing Solution (Nov 2025):</strong></p>
<p>Added per-period day volatility attributes to detect when classification changes are meaningful:</p>
<ul>
<li class=""><code>day_volatility_%</code>: Percentage spread (span/avg × 100)</li>
<li class=""><code>day_price_min</code>, <code>day_price_max</code>, <code>day_price_span</code>: Daily price range (ct/øre)</li>
</ul>
<p>Automations can check volatility before acting:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">condition</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">condition</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> template</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">value_template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token scalar string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> {{ state_attr(&#x27;binary_sensor.tibber_home_best_price_period&#x27;, &#x27;day_volatility_%&#x27;) | float(0) &gt; 15 }}</span><br></span></code></pre></div></div>
<p>Low volatility (&lt; 15%) means classification changes are less economically significant.</p>
<p><strong>Alternative Approaches Rejected:</strong></p>
<ol>
<li class="">
<p><strong>Use period start day for all intervals</strong></p>
<ul>
<li class="">Problem: Mathematically incorrect - lends cheap day&#x27;s criteria to expensive day</li>
<li class="">Rejected: Violates relative evaluation principle</li>
</ul>
</li>
<li class="">
<p><strong>Adjust flex/distance at midnight</strong></p>
<ul>
<li class="">Problem: Complex, unpredictable, hides market reality</li>
<li class="">Rejected: Users should understand price context, not have it hidden</li>
</ul>
</li>
<li class="">
<p><strong>Split at midnight always</strong></p>
<ul>
<li class="">Problem: Artificially fragments natural periods</li>
<li class="">Rejected: Worse user experience</li>
</ul>
</li>
<li class="">
<p><strong>Use next day&#x27;s reference after midnight</strong></p>
<ul>
<li class="">Problem: Period criteria inconsistent across duration</li>
<li class="">Rejected: Confusing and unpredictable</li>
</ul>
</li>
</ol>
<p><strong>Status:</strong> Per-day evaluation is intentional design prioritizing mathematical correctness.</p>
<p><strong>See Also:</strong></p>
<ul>
<li class="">User documentation: <code>docs/user/period-calculation.md</code>&quot;Midnight Price Classification Changes&quot;</li>
<li class="">Implementation: <code>coordinator/period_handlers/period_building.py</code> (line ~126: <code>ref_date = date_key</code>)</li>
<li class="">Attributes: <code>coordinator/period_handlers/period_statistics.py</code> (day volatility calculation)</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no"></a></h2>
<ul>
<li class=""><a href="https://jpawlowski.github.io/hass.tibber_prices/user/period-calculation" target="_blank" rel="noopener noreferrer" class="">User Documentation: Period Calculation</a></li>
<li class=""><a class="" href="/hass.tibber_prices/developer/architecture">Architecture Overview</a></li>
<li class=""><a class="" href="/hass.tibber_prices/developer/caching-strategy">Caching Strategy</a></li>
<li class=""><a href="https://github.com/jpawlowski/hass.tibber_prices/blob/main/AGENTS.md" target="_blank" rel="noopener noreferrer" class="">AGENTS.md</a> - AI assistant memory (implementation patterns)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="changelog">Changelog<a href="#changelog" class="hash-link" aria-label="Direct link to Changelog" title="Direct link to Changelog" translate="no"></a></h2>
<ul>
<li class=""><strong>2025-11-19</strong>: Initial documentation of Flex/Distance interaction and Relaxation strategy fixes</li>
</ul></div><footer class="theme-doc-footer docusaurus-mt-lg"><div class="row margin-top--sm theme-doc-footer-edit-meta-row"><div class="col noPrint_WFHX"><a href="https://github.com/jpawlowski/hass.tibber_prices/tree/main/docs/developer/docs/period-calculation-theory.md" target="_blank" rel="noopener noreferrer" class="theme-edit-this-page"><svg fill="currentColor" height="20" width="20" viewBox="0 0 40 40" class="iconEdit_Z9Sw" aria-hidden="true"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div><div class="col lastUpdated_JAkA"><span class="theme-last-updated">Last updated<!-- --> on <b><time datetime="2025-12-06T01:37:06.000Z" itemprop="dateModified">Dec 6, 2025</time></b></span></div></div></footer></article><nav class="docusaurus-mt-lg pagination-nav" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/hass.tibber_prices/developer/debugging"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Debugging Guide</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/hass.tibber_prices/developer/refactoring-guide"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Refactoring Guide</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#overview" class="table-of-contents__link toc-highlight">Overview</a></li><li><a href="#core-filtering-criteria" class="table-of-contents__link toc-highlight">Core Filtering Criteria</a><ul><li><a href="#1-flex-filter-price-distance-from-reference" class="table-of-contents__link toc-highlight">1. Flex Filter (Price Distance from Reference)</a></li><li><a href="#2-min-distance-filter-distance-from-daily-average" class="table-of-contents__link toc-highlight">2. Min Distance Filter (Distance from Daily Average)</a></li><li><a href="#3-level-filter-price-level-classification" class="table-of-contents__link toc-highlight">3. Level Filter (Price Level Classification)</a></li></ul></li><li><a href="#the-flex--min_distance-conflict" class="table-of-contents__link toc-highlight">The Flex × Min_Distance Conflict</a><ul><li><a href="#problem-statement" class="table-of-contents__link toc-highlight">Problem Statement</a></li><li><a href="#mathematical-analysis" class="table-of-contents__link toc-highlight">Mathematical Analysis</a></li><li><a href="#solution-dynamic-min_distance-scaling" class="table-of-contents__link toc-highlight">Solution: Dynamic Min_Distance Scaling</a></li></ul></li><li><a href="#flex-limits-and-safety-caps" class="table-of-contents__link toc-highlight">Flex Limits and Safety Caps</a><ul><li><a href="#implementation-constants" class="table-of-contents__link toc-highlight">Implementation Constants</a></li><li><a href="#rationale-for-asymmetric-defaults" class="table-of-contents__link toc-highlight">Rationale for Asymmetric Defaults</a></li></ul></li><li><a href="#flex-limits-and-safety-caps-1" class="table-of-contents__link toc-highlight">Flex Limits and Safety Caps</a><ul><li><a href="#recommended-ranges-user-guidance" class="table-of-contents__link toc-highlight">Recommended Ranges (User Guidance)</a></li></ul></li><li><a href="#relaxation-strategy" class="table-of-contents__link toc-highlight">Relaxation Strategy</a><ul><li><a href="#purpose" class="table-of-contents__link toc-highlight">Purpose</a></li><li><a href="#multi-phase-approach" class="table-of-contents__link toc-highlight">Multi-Phase Approach</a></li><li><a href="#relaxation-increments" class="table-of-contents__link toc-highlight">Relaxation Increments</a></li><li><a href="#filter-combination-strategy" class="table-of-contents__link toc-highlight">Filter Combination Strategy</a></li></ul></li><li><a href="#implementation-notes" class="table-of-contents__link toc-highlight">Implementation Notes</a><ul><li><a href="#key-files-and-functions" class="table-of-contents__link toc-highlight">Key Files and Functions</a></li></ul></li><li><a href="#debugging-tips" class="table-of-contents__link toc-highlight">Debugging Tips</a></li><li><a href="#common-configuration-pitfalls" class="table-of-contents__link toc-highlight">Common Configuration Pitfalls</a><ul><li><a href="#-anti-pattern-1-high-flex-with-relaxation" class="table-of-contents__link toc-highlight">❌ Anti-Pattern 1: High Flex with Relaxation</a></li><li><a href="#-anti-pattern-2-zero-min_distance" class="table-of-contents__link toc-highlight">❌ Anti-Pattern 2: Zero Min_Distance</a></li><li><a href="#-anti-pattern-3-conflicting-flex--distance" class="table-of-contents__link toc-highlight">❌ Anti-Pattern 3: Conflicting Flex + Distance</a></li></ul></li><li><a href="#testing-scenarios" class="table-of-contents__link toc-highlight">Testing Scenarios</a><ul><li><a href="#scenario-1-normal-day-good-variation" class="table-of-contents__link toc-highlight">Scenario 1: Normal Day (Good Variation)</a></li><li><a href="#scenario-2-flat-day-poor-variation" class="table-of-contents__link toc-highlight">Scenario 2: Flat Day (Poor Variation)</a></li><li><a href="#scenario-3-extreme-day-high-volatility" class="table-of-contents__link toc-highlight">Scenario 3: Extreme Day (High Volatility)</a></li><li><a href="#scenario-4-relaxation-success" class="table-of-contents__link toc-highlight">Scenario 4: Relaxation Success</a></li><li><a href="#scenario-5-relaxation-exhausted" class="table-of-contents__link toc-highlight">Scenario 5: Relaxation Exhausted</a></li><li><a href="#debugging-checklist" class="table-of-contents__link toc-highlight">Debugging Checklist</a></li></ul></li><li><a href="#future-enhancements" class="table-of-contents__link toc-highlight">Future Enhancements</a><ul><li><a href="#potential-improvements" class="table-of-contents__link toc-highlight">Potential Improvements</a></li><li><a href="#known-limitations" class="table-of-contents__link toc-highlight">Known Limitations</a></li></ul></li><li><a href="#future-enhancements-1" class="table-of-contents__link toc-highlight">Future Enhancements</a><ul><li><a href="#potential-improvements-1" class="table-of-contents__link toc-highlight">Potential Improvements</a></li><li><a href="#known-limitations-1" class="table-of-contents__link toc-highlight">Known Limitations</a></li></ul></li><li><a href="#references" class="table-of-contents__link toc-highlight">References</a></li><li><a href="#changelog" class="table-of-contents__link toc-highlight">Changelog</a></li></ul></div></div></div></div></main></div></div></div><footer class="theme-layout-footer footer footer--dark"><div class="container container-fluid"><div class="row footer__links"><div class="theme-layout-footer-column col footer__col"><div class="footer__title">Documentation</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://jpawlowski.github.io/hass.tibber_prices/user/" target="_blank" rel="noopener noreferrer" class="footer__link-item">User Guide<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li><li class="footer__item"><a class="footer__link-item" href="/hass.tibber_prices/developer/intro">Developer Guide</a></li></ul></div><div class="theme-layout-footer-column col footer__col"><div class="footer__title">Community</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://github.com/jpawlowski/hass.tibber_prices/issues" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub Issues<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li><li class="footer__item"><a href="https://community.home-assistant.io/" target="_blank" rel="noopener noreferrer" class="footer__link-item">Home Assistant Community<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li></ul></div><div class="theme-layout-footer-column col footer__col"><div class="footer__title">More</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://github.com/jpawlowski/hass.tibber_prices" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li><li class="footer__item"><a href="https://github.com/jpawlowski/hass.tibber_prices/releases" target="_blank" rel="noopener noreferrer" class="footer__link-item">Release Notes<svg width="13.5" height="13.5" aria-label="(opens in new tab)" class="iconExternalLink_nPIU"><use href="#theme-svg-external-link"></use></svg></a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">Not affiliated with Tibber AS. Community-maintained custom integration. Built with Docusaurus.</div></div></div></footer></div>
</body>
</html>