@@ -424,13 +424,6 @@ const STYLES = `
424424 }
425425 .apply-summary strong { color: var(--error-color, #db4437); }
426426 code { font-family: monospace; background: var(--secondary-background-color, #f5f5f5); padding: 1px 4px; border-radius: 3px; font-size: 0.85em; }
427- #chart-container {
428- margin: 0 0 14px;
429- }
430- #chart-container ha-chart-base {
431- display: block;
432- width: 100%;
433- }
434427` ;
435428
436429class StatisticsOutlierCleanerPanel extends HTMLElement {
@@ -447,10 +440,6 @@ class StatisticsOutlierCleanerPanel extends HTMLElement {
447440 this . _allStats = [ ] ; // full list from WS
448441 this . _activeIdx = - 1 ; // keyboard nav index in dropdown
449442 this . _recentStats = this . _loadRecentStats ( ) ;
450- this . _series = [ ] ;
451- this . _chartEl = null ;
452- this . _scanStartTs = null ;
453- this . _scanEndTs = null ;
454443 this . _onDocClick = ( e ) => {
455444 if ( ! this . shadowRoot . contains ( e . target ) ) this . _closeDropdown ( ) ;
456445 } ;
@@ -588,7 +577,6 @@ class StatisticsOutlierCleanerPanel extends HTMLElement {
588577 <div id="results-card" class="card hidden">
589578 <h3>Detected Outliers</h3>
590579 <div id="scan-stats-row" class="scan-stats-row hidden"></div>
591- <div id="chart-container" class="hidden"></div>
592580 <details id="scan-detail" class="hidden" style="margin-bottom:10px">
593581 <summary style="cursor:pointer;font-size:0.75rem;color:var(--secondary-text-color);user-select:none">Statistical detail</summary>
594582 <div id="scan-meta" class="meta" style="margin-top:4px"></div>
@@ -844,156 +832,6 @@ class StatisticsOutlierCleanerPanel extends HTMLElement {
844832 return this . _hass . connection . sendMessagePromise ( { id : this . _msgId ++ , ...msg } ) ;
845833 }
846834
847- async _fetchSeries ( statId , startTs , endTs ) {
848- try {
849- const result = await this . _hass . connection . sendMessagePromise ( {
850- id : this . _msgId ++ ,
851- type : "recorder/statistics_during_period" ,
852- start_time : new Date ( startTs * 1000 ) . toISOString ( ) ,
853- end_time : new Date ( endTs * 1000 ) . toISOString ( ) ,
854- statistic_ids : [ statId ] ,
855- period : "hour" ,
856- types : [ "sum" ] ,
857- } ) ;
858- const rows = result [ statId ] || [ ] ;
859- this . _series = rows
860- . filter ( r => r . sum != null )
861- . map ( r => ( { start : new Date ( r . start ) . getTime ( ) , sum : r . sum } ) ) ;
862- } catch ( e ) {
863- console . warn ( "[outlier-cleaner] series fetch failed:" , e ) ;
864- this . _series = [ ] ;
865- }
866- }
867-
868- _computeAfterSeries ( ) {
869- const replacement = parseFloat ( this . _q ( "replacement" ) ?. value ?? "0" ) || 0 ;
870- const sums = this . _series . map ( r => r . sum ) ;
871- const selected = [ ...this . _selected ]
872- . map ( i => this . _candidates [ i ] )
873- . sort ( ( a , b ) => a . start - b . start ) ;
874- for ( const c of selected ) {
875- const delta = replacement - c . change ;
876- const idx = this . _series . findIndex ( r => r . start >= c . start ) ;
877- if ( idx === - 1 ) continue ;
878- for ( let j = idx ; j < sums . length ; j ++ ) sums [ j ] += delta ;
879- }
880- return sums ;
881- }
882-
883- // ---------------------------------------------------------------------------
884- // Chart
885- // ---------------------------------------------------------------------------
886-
887- async _renderChart ( ) {
888- const container = this . _q ( "chart-container" ) ;
889- if ( ! container || ! this . _series . length ) return ;
890-
891- try {
892- await Promise . race ( [
893- customElements . whenDefined ( "ha-chart-base" ) ,
894- new Promise ( ( _ , reject ) =>
895- setTimeout ( ( ) => reject ( new Error ( "timeout" ) ) , 3000 )
896- ) ,
897- ] ) ;
898- } catch ( _ ) {
899- console . warn ( "[outlier-cleaner] ha-chart-base unavailable, skipping chart" ) ;
900- return ;
901- }
902-
903- const labels = this . _series . map ( r =>
904- new Date ( r . start ) . toLocaleString ( [ ] , {
905- month : "short" , day : "numeric" , hour : "2-digit" , minute : "2-digit" ,
906- } )
907- ) ;
908- const beforeSums = this . _series . map ( r => r . sum ) ;
909- const afterSums = this . _computeAfterSeries ( ) ;
910-
911- const candidateMs = new Set ( this . _candidates . map ( c => c . start ) ) ;
912- const selectedMs = new Set ( [ ...this . _selected ] . map ( i => this . _candidates [ i ] . start ) ) ;
913- const pointRadius = this . _series . map ( r =>
914- candidateMs . has ( r . start ) ? 5 : 0
915- ) ;
916- const pointBgColor = this . _series . map ( r => {
917- const ms = r . start ;
918- if ( ! candidateMs . has ( ms ) ) return "transparent" ;
919- return selectedMs . has ( ms ) ? "#ef4444" : "transparent" ;
920- } ) ;
921- const pointBorderColor = this . _series . map ( r =>
922- candidateMs . has ( r . start ) ? "#ef4444" : "transparent"
923- ) ;
924-
925- const el = document . createElement ( "ha-chart-base" ) ;
926- // Connect to DOM before setting data so Lit can initialise with hass context.
927- container . innerHTML = "" ;
928- container . appendChild ( el ) ;
929- container . classList . remove ( "hidden" ) ;
930-
931- el . hass = this . _hass ;
932- el . chartType = "line" ;
933- el . data = {
934- labels,
935- datasets : [
936- {
937- label : "Before" ,
938- data : beforeSums ,
939- borderColor : "#ef4444" ,
940- borderDash : [ 5 , 4 ] ,
941- borderWidth : 1.5 ,
942- pointRadius,
943- pointBackgroundColor : pointBgColor ,
944- pointBorderColor,
945- pointBorderWidth : 2 ,
946- tension : 0 ,
947- fill : false ,
948- } ,
949- {
950- label : "After (simulated)" ,
951- data : afterSums ,
952- borderColor : "#4ade80" ,
953- borderWidth : 1.5 ,
954- pointRadius : 0 ,
955- tension : 0 ,
956- fill : false ,
957- } ,
958- ] ,
959- } ;
960- el . options = {
961- animation : false ,
962- responsive : true ,
963- maintainAspectRatio : true ,
964- aspectRatio : 3 ,
965- plugins : {
966- legend : { display : false } ,
967- tooltip : { mode : "index" , intersect : false } ,
968- } ,
969- scales : {
970- x : { ticks : { maxTicksLimit : 8 , maxRotation : 0 } , grid : { display : false } } ,
971- y : { beginAtZero : false } ,
972- } ,
973- } ;
974-
975- this . _chartEl = el ;
976- }
977-
978- _updateChart ( ) {
979- if ( ! this . _chartEl || ! this . _series . length ) return ;
980- const afterSums = this . _computeAfterSeries ( ) ;
981- const selectedMs = new Set ( [ ...this . _selected ] . map ( i => this . _candidates [ i ] . start ) ) ;
982- const candidateMs = new Set ( this . _candidates . map ( c => c . start ) ) ;
983- const pointBgColor = this . _series . map ( r => {
984- const ms = r . start ;
985- if ( ! candidateMs . has ( ms ) ) return "transparent" ;
986- return selectedMs . has ( ms ) ? "#ef4444" : "transparent" ;
987- } ) ;
988- this . _chartEl . data = {
989- labels : this . _chartEl . data . labels ,
990- datasets : [
991- { ...this . _chartEl . data . datasets [ 0 ] , pointBackgroundColor : pointBgColor } ,
992- { ...this . _chartEl . data . datasets [ 1 ] , data : afterSums } ,
993- ] ,
994- } ;
995- }
996-
997835 // ---------------------------------------------------------------------------
998836 // Scan
999837 // ---------------------------------------------------------------------------
@@ -1025,21 +863,14 @@ class StatisticsOutlierCleanerPanel extends HTMLElement {
1025863 if ( method === "absolute" ) params . threshold = parseFloat ( this . _q ( "threshold" ) . value ) || 0 ;
1026864 if ( method === "top_n" ) params . top_n = parseInt ( this . _q ( "top-n" ) . value ) || 10 ;
1027865
1028- this . _scanStartTs = params . start_ts || ( Date . now ( ) / 1000 - 86_400 * 30 ) ;
1029- this . _scanEndTs = params . end_ts || ( Date . now ( ) / 1000 ) ;
1030-
1031866 this . _showStatus ( "info" , "Scanning…" ) ;
1032867 this . _q ( "btn-scan" ) . disabled = true ;
1033868
1034869 try {
1035- const [ result ] = await Promise . all ( [
1036- this . _send ( params ) ,
1037- this . _fetchSeries ( statId , this . _scanStartTs , this . _scanEndTs ) ,
1038- ] ) ;
870+ const result = await this . _send ( params ) ;
1039871 this . _candidates = result . candidates || [ ] ;
1040872 this . _selected = new Set ( this . _candidates . map ( ( _ , i ) => i ) ) ;
1041873 this . _renderResults ( result ) ;
1042- this . _renderChart ( ) ;
1043874 this . _clearStatus ( ) ;
1044875 const statMeta = this . _allStats . find ( ( s ) => s . statistic_id === statId ) ;
1045876 this . _saveRecentStat ( statId , statMeta ?. name || null ) ;
@@ -1106,7 +937,6 @@ class StatisticsOutlierCleanerPanel extends HTMLElement {
1106937 ? `Preview: would replace <strong>${ n } reading${ n !== 1 ? "s" : "" } </strong> with <strong>${ replacement } </strong> — no DB changes`
1107938 : `Replace <strong>${ n } reading${ n !== 1 ? "s" : "" } </strong> with <strong>${ replacement } </strong>` ;
1108939 applyBtn . textContent = isDry ? `Preview ${ n } rows` : `Apply to ${ n } rows` ;
1109- this . _updateChart ( ) ;
1110940 }
1111941
1112942 _renderTable ( ) {
@@ -1226,8 +1056,6 @@ class StatisticsOutlierCleanerPanel extends HTMLElement {
12261056 this . _q ( "apply-area" ) . classList . add ( "hidden" ) ;
12271057 }
12281058 this . _loadHistory ( ) ;
1229- await this . _fetchSeries ( this . _statId || statId , this . _scanStartTs , this . _scanEndTs ) ;
1230- this . _renderChart ( ) ;
12311059 }
12321060 } catch ( e ) {
12331061 this . _showStatus ( "error" , `Apply failed: ${ e . message || JSON . stringify ( e ) } ` ) ;
0 commit comments