feat: compute Solar 8760 averages at year/month/day levels
Some checks are pending
CI / Engine — lint / typecheck / test (push) Waiting to run
CI / API — lint / typecheck / test (push) Waiting to run
CI / Web — typecheck / lint / build (push) Waiting to run

- 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:
Manohar Gupta 2026-05-16 14:37:34 +05:30
parent ff446bc34a
commit 2705f4bb11

View file

@ -600,7 +600,7 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
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 data = isSolar ? solar_hourly : wind_hourly;
if (!data) return [];
@ -623,6 +623,51 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
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
const computeYearTotal = (year: number, isSolar: boolean) => {
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 clientEndYr = hasClientEnd ? computeYearTotalNew(year, hourly_client_end) : 0;
const loadYr = hasLoad ? computeYearTotalNew(year, hourly_load) : 0;
// For profile totals - sum of hourly profile values = total energy before capacity division
const solarProfileSum = hasSolarProfile ? computeYearTotalNew(year, hourly_solar_profile) : 0;
const windProfileSum = hasWindProfile ? computeYearTotalNew(year, hourly_wind_profile) : 0;
// Solar 8760: average profile value (0-1), DC MW: capacity (placeholder), Solar MW: sum output
const solarProfileAvg = hasSolarProfile ? computeYearAvgProfile(year, hourly_solar_profile) : 0;
const fyLabel = getFyLabel(i);
return (
@ -724,11 +768,13 @@ function HourlyGenerationSheet({ hourly, codYear }: { hourly: HourlyData; codYea
>
<span className="text-[10px] w-4">{isYearExpanded ? "▼" : "▶"}</span>
<span className="w-20 font-medium">{fyLabel}</span>
{hasSolar && <span className="text-orange-700 w-20">{Math.round(solarProfileSum).toLocaleString()}</span>}
{hasSolar && <span className="text-orange-600/70 w-16">-</span>}
{/* Solar 8760: avg profile (show as %), DC MW, Solar MW */}
{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>}
{hasWind && <span className="text-blue-700 w-20">{Math.round(windProfileSum).toLocaleString()}</span>}
{hasWind && <span className="text-blue-600/70 w-16">-</span>}
{/* Wind 8760, MW, Wind MW */}
{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>}
{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>}