@@ -24,7 +24,7 @@ import { TIMELINE_MODES } from '../../timeline/timeline-modes';
2424import { buildPeriodData , binSlotSum , type PeriodHost , type PeriodLayer } from './period-totals' ;
2525import { changeSeriesToWatts } from '../sources/energy-stats' ;
2626import { groupDevices } from '../sources/device-consumption' ;
27- import { staticPrice } from '../sources/cost' ;
27+ import { staticPrice , costRateAt } from '../sources/cost' ;
2828import type { PeriodWindow } from './slot-walk' ;
2929
3030//One drawn line of the day, unit-agnostic. `colour` is a descriptor the card resolves; everything else is the data
@@ -467,22 +467,37 @@ export function buildDayProfile(host: PeriodHost, target: ChartTarget, dayMs: nu
467467
468468 if ( target === 'cost' )
469469 {
470- const store = host . _unifiedStore ;
471- if ( ! store ) { return [ ] ; }
472- //Static price only for now (cost = energy x constant price, exact across the whole history). The ring shows
473- //the SPENDING magnitude per slot (currency/hour, floored at 0); earning hours read as no ring, since the
474- //around-house ring is a positive-magnitude shape. store powers are watts, hence /1000.
475- const impP = staticPrice ( host . _energyDefaults . gridImportPriceNumbers ) ;
476- if ( impP === null ) { return [ ] ; }
477- const expP = staticPrice ( host . _energyDefaults . gridExportPriceNumbers ) ?? 0 ;
478- const imp = binSlotAvg ( store , store . gridImport , slots , win ) ;
479- const exp = binSlotAvg ( store , store . gridExport , slots , win ) ;
480- const values = imp . map ( ( iv , i ) =>
470+ //The ring shows the SPENDING magnitude per slot (currency/hour, floored at 0); earning hours read as no
471+ //ring, since the around-house ring is a positive-magnitude shape. Preferred source: HA's cost statistics
472+ //(any tariff), sampled at each slot's midpoint. Fallback: a single static price x grid energy (store
473+ //powers are watts, hence /1000).
474+ const hasStats = ! ! ( host . _costImportSeries || host . _costExportSeries ) ;
475+ let values : ( number | null ) [ ] ;
476+ if ( hasStats )
481477 {
482- const ev = exp [ i ] ;
483- if ( ( iv === null || ! isFinite ( iv ) ) && ( ev === null || ! isFinite ( ev ) ) ) { return null ; }
484- return Math . max ( 0 , ( ( iv ?? 0 ) * impP - ( ev ?? 0 ) * expP ) / 1000 ) ;
485- } ) ;
478+ const slotLen = DAY_MS / slots ;
479+ values = Array . from ( { length : slots } , ( _v , s ) =>
480+ {
481+ const r = costRateAt ( host , win . fromMs + ( s + 0.5 ) * slotLen ) ;
482+ return r === null ? null : Math . max ( 0 , r ) ;
483+ } ) ;
484+ }
485+ else
486+ {
487+ const store = host . _unifiedStore ;
488+ if ( ! store ) { return [ ] ; }
489+ const impP = staticPrice ( host . _energyDefaults . gridImportPriceNumbers ) ;
490+ if ( impP === null ) { return [ ] ; }
491+ const expP = staticPrice ( host . _energyDefaults . gridExportPriceNumbers ) ?? 0 ;
492+ const imp = binSlotAvg ( store , store . gridImport , slots , win ) ;
493+ const exp = binSlotAvg ( store , store . gridExport , slots , win ) ;
494+ values = imp . map ( ( iv , i ) =>
495+ {
496+ const ev = exp [ i ] ;
497+ if ( ( iv === null || ! isFinite ( iv ) ) && ( ev === null || ! isFinite ( ev ) ) ) { return null ; }
498+ return Math . max ( 0 , ( ( iv ?? 0 ) * impP - ( ev ?? 0 ) * expP ) / 1000 ) ;
499+ } ) ;
500+ }
486501 dropShortRuns ( values ) ;
487502 const peak = peakOf ( values ) ;
488503 if ( peak <= 0 ) { return [ ] ; }
0 commit comments