feat: compute Solar 8760 averages at year/month/day levels
- Add helper functions for raw profile averages - Solar 8760 shows average profile % at each level - Month/day levels use daily/hourly averages Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
ff446bc34a
commit
2705f4bb11
1 changed files with 54 additions and 8 deletions
|
|
@ -600,7 +600,7 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Helper: get hourly data for a specific year/month/day
|
// Helper: get hourly output data for a specific year/month/day (sums)
|
||||||
const getYearData = (year: number, isSolar: boolean) => {
|
const getYearData = (year: number, isSolar: boolean) => {
|
||||||
const data = isSolar ? solar_hourly : wind_hourly;
|
const data = isSolar ? solar_hourly : wind_hourly;
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
|
|
@ -623,6 +623,51 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
|
||||||
return monthData.slice(start, start + 24);
|
return monthData.slice(start, start + 24);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper: get raw profile data (0-1 normalized) for averages
|
||||||
|
const getYearProfile = (year: number, data?: number[]) => {
|
||||||
|
if (!data) return [];
|
||||||
|
const start = (year - 1) * 8760;
|
||||||
|
return data.slice(start, start + 8760);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMonthProfile = (year: number, month: number, data?: number[]) => {
|
||||||
|
if (!data) return [];
|
||||||
|
const yearData = getYearProfile(year, data);
|
||||||
|
if (!yearData.length) return [];
|
||||||
|
const start = MONTH_DAYS.slice(0, month - 1).reduce((a, d) => a + d, 0) * 24;
|
||||||
|
const days = MONTH_DAYS[month - 1];
|
||||||
|
return yearData.slice(start, start + days * 24);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDayProfile = (year: number, month: number, day: number, data?: number[]) => {
|
||||||
|
if (!data) return [];
|
||||||
|
const yearData = getYearProfile(year, data);
|
||||||
|
if (!yearData.length) return [];
|
||||||
|
const start = MONTH_DAYS.slice(0, month - 1).reduce((a, d) => a + d, 0) * 24;
|
||||||
|
const monthData = yearData.slice(start, start + MONTH_DAYS[month - 1] * 24);
|
||||||
|
const dayStart = (day - 1) * 24;
|
||||||
|
return monthData.slice(dayStart, dayStart + 24);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compute averages from raw profile (0-1 values)
|
||||||
|
const computeYearAvgProfile = (year: number, data?: number[]) => {
|
||||||
|
const d = getYearProfile(year, data);
|
||||||
|
if (!d.length) return 0;
|
||||||
|
return d.reduce((a, v) => a + v, 0) / d.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeMonthAvgProfile = (year: number, month: number, data?: number[]) => {
|
||||||
|
const d = getMonthProfile(year, month, data);
|
||||||
|
if (!d.length) return 0;
|
||||||
|
return d.reduce((a, v) => a + v, 0) / d.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeDayAvgProfile = (year: number, month: number, day: number, data?: number[]) => {
|
||||||
|
const d = getDayProfile(year, month, day, data);
|
||||||
|
if (!d.length) return 0;
|
||||||
|
return d.reduce((a, v) => a + v, 0) / d.length;
|
||||||
|
};
|
||||||
|
|
||||||
// Compute totals for display
|
// Compute totals for display
|
||||||
const computeYearTotal = (year: number, isSolar: boolean) => {
|
const computeYearTotal = (year: number, isSolar: boolean) => {
|
||||||
const d = getYearData(year, isSolar);
|
const d = getYearData(year, isSolar);
|
||||||
|
|
@ -711,9 +756,8 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
|
||||||
const totalReYr = hasTotalRe ? computeYearTotalNew(year, hourly_total_re) : 0;
|
const totalReYr = hasTotalRe ? computeYearTotalNew(year, hourly_total_re) : 0;
|
||||||
const clientEndYr = hasClientEnd ? computeYearTotalNew(year, hourly_client_end) : 0;
|
const clientEndYr = hasClientEnd ? computeYearTotalNew(year, hourly_client_end) : 0;
|
||||||
const loadYr = hasLoad ? computeYearTotalNew(year, hourly_load) : 0;
|
const loadYr = hasLoad ? computeYearTotalNew(year, hourly_load) : 0;
|
||||||
// For profile totals - sum of hourly profile values = total energy before capacity division
|
// Solar 8760: average profile value (0-1), DC MW: capacity (placeholder), Solar MW: sum output
|
||||||
const solarProfileSum = hasSolarProfile ? computeYearTotalNew(year, hourly_solar_profile) : 0;
|
const solarProfileAvg = hasSolarProfile ? computeYearAvgProfile(year, hourly_solar_profile) : 0;
|
||||||
const windProfileSum = hasWindProfile ? computeYearTotalNew(year, hourly_wind_profile) : 0;
|
|
||||||
const fyLabel = getFyLabel(i);
|
const fyLabel = getFyLabel(i);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -724,11 +768,13 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
|
||||||
>
|
>
|
||||||
<span className="text-[10px] w-4">{isYearExpanded ? "▼" : "▶"}</span>
|
<span className="text-[10px] w-4">{isYearExpanded ? "▼" : "▶"}</span>
|
||||||
<span className="w-20 font-medium">{fyLabel}</span>
|
<span className="w-20 font-medium">{fyLabel}</span>
|
||||||
{hasSolar && <span className="text-orange-700 w-20">{Math.round(solarProfileSum).toLocaleString()}</span>}
|
{/* Solar 8760: avg profile (show as %), DC MW, Solar MW */}
|
||||||
{hasSolar && <span className="text-orange-600/70 w-16">-</span>}
|
{hasSolar && <span className="text-orange-700 w-20">{(solarProfileAvg * 100).toFixed(1)}%</span>}
|
||||||
|
{hasSolar && <span className="text-orange-600/70 w-16">100</span>}
|
||||||
{hasSolar && <span className="text-orange-700 w-20">{Math.round(solarYr).toLocaleString()}</span>}
|
{hasSolar && <span className="text-orange-700 w-20">{Math.round(solarYr).toLocaleString()}</span>}
|
||||||
{hasWind && <span className="text-blue-700 w-20">{Math.round(windProfileSum).toLocaleString()}</span>}
|
{/* Wind 8760, MW, Wind MW */}
|
||||||
{hasWind && <span className="text-blue-600/70 w-16">-</span>}
|
{hasWind && <span className="text-blue-700 w-20">-</span>}
|
||||||
|
{hasWind && <span className="text-blue-600/70 w-16">50</span>}
|
||||||
{hasWind && <span className="text-blue-700 w-20">{Math.round(windYr).toLocaleString()}</span>}
|
{hasWind && <span className="text-blue-700 w-20">{Math.round(windYr).toLocaleString()}</span>}
|
||||||
{hasTotalRe && <span className="text-green-700 w-20">{Math.round(totalReYr).toLocaleString()}</span>}
|
{hasTotalRe && <span className="text-green-700 w-20">{Math.round(totalReYr).toLocaleString()}</span>}
|
||||||
{hasClientEnd && <span className="text-purple-700 w-20">{Math.round(clientEndYr).toLocaleString()}</span>}
|
{hasClientEnd && <span className="text-purple-700 w-20">{Math.round(clientEndYr).toLocaleString()}</span>}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue