mirror of
https://github.com/jpawlowski/hass.tibber_prices.git
synced 2026-03-29 21:03:40 +00:00
825 lines
No EOL
163 KiB
HTML
825 lines
No EOL
163 KiB
HTML
<!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&family=Space+Grotesk:wght@500;600;700&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"><=</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">>=</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"><=</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">>=</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 < 10%, MEDIUM < 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="volatility_low"</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 < 10%, MEDIUM < 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 ("I want to see LOW at 15% instead of 10%")</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">"volatility_low"</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 > 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) > 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) > 14.25</code></li>
|
||
<li class="">Simplify: <code>flex > 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">></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 "flat days" (little price variation) from accepting every interval</li>
|
||
<li class="">Maintains semantic meaning: "this is a meaningful best/peak price period"</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 > 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">></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"><</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">"High flex %.1f%% detected: Reducing min_distance %.1f%% → %.1f%%"</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"><=</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">>=</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 "every interval is a period" scenario</li>
|
||
<li class="">Maintains user expectation: "best/peak price means notably different"</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 "slightly below average"</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: "Why didn't I get warned about the 30-min price spike?"</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 "stable context" threshold</li>
|
||
<li class="">At > 25% Flex, almost any price swing is considered "stable"</li>
|
||
<li class=""><strong>Result:</strong> Legitimate price shifts aren't smoothed, breaking period formation</li>
|
||
</ul>
|
||
<p><strong>Note:</strong> User'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> > 25%</p>
|
||
<ul>
|
||
<li class="">INFO log: "Base flex is on the high side"</li>
|
||
</ul>
|
||
<p><strong>High Warning:</strong> > 30%</p>
|
||
<ul>
|
||
<li class="">WARNING log: "Base flex is very high for relaxation mode!"</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'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'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">>=</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">"Base flex %.1f%% is very high for relaxation mode! "</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">"Consider lowering to 15-20%% or disabling relaxation."</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">>=</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">"Base flex %.1f%% is on the high side. "</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">"Consider 15-20%% for optimal relaxation effectiveness."</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 = "any" (disabled)</li>
|
||
</ol>
|
||
<p><strong>Early Exit:</strong> Stop immediately when target reached (don'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">></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">></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">></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">></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| > 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">></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">></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><</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 > 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 (>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'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: "expensive during peak hours"</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'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>"Filter statistics: X intervals checked"</code> - Shows how many intervals filtered by each criterion</li>
|
||
<li class=""><code>"After build_periods: X raw periods found"</code> - Periods before min_length filtering</li>
|
||
<li class=""><code>"Day X: Success with flex=Y%"</code> - Relaxation succeeded</li>
|
||
<li class=""><code>"High flex X% detected: Reducing min_distance Y% → Z%"</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="">"Flat days" (little price variation) accept all intervals</li>
|
||
<li class="">Periods lose semantic meaning ("significantly cheap")</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 "best price"</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 < 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 (>30%) = Need more flexibility or relaxation</li>
|
||
<li class="">High distance filtering (>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 "Baseline insufficient" 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'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 "High flex X% detected: Reducing min_distance Y% → Z%"</li>
|
||
<li class="">If scale factor <code><</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 "Outlier detected" 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'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"><</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 (< 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">></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 (> 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'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'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 "why this period?"</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'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'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><</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 "best price" 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 "spread" parameter (prefer distributed periods)</li>
|
||
<li class="">Weight periods by time-of-day preferences</li>
|
||
<li class="">Consider user'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'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'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'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'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's 11.5 ct threshold</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 21 ct > 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's own day):</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 00:00 evaluated against Day 2'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">></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('binary_sensor.tibber_home_best_price_period', 'day_volatility_%') | float(0) > 15 }}</span><br></span></code></pre></div></div>
|
||
<p>Low volatility (< 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'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'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> → "Midnight Price Classification Changes"</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> |