Created
February 27, 2026 15:45
-
-
Save ismasan/49e0d3f41b13282541bc1c27e7f364a3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Energy | |
| class Comparison | |
| module LoadProfileGapFiller | |
| module_function | |
| # Public API | |
| def fill_gaps(annual_coefficients:, customer_profile:) | |
| # 1. Compute observed totals | |
| observed_total = customer_profile.values.sum | |
| raise "Customer profile is empty" if observed_total.zero? | |
| # 2. Aggregate national + customer to monthly totals | |
| national_monthly = monthly_totals(annual_coefficients) | |
| customer_monthly = monthly_totals(customer_profile) | |
| # 3. Compute monthly bias factors | |
| monthly_bias = compute_monthly_bias( | |
| national_monthly: national_monthly, | |
| customer_monthly: customer_monthly, | |
| observed_total: observed_total | |
| ) | |
| # 4. Build adjusted coefficient curve | |
| adjusted_coefficients = apply_monthly_bias( | |
| annual: annual_coefficients, | |
| monthly_bias: monthly_bias | |
| ) | |
| # 5. Renormalise adjusted coefficients to sum to 1 | |
| renormalised = renormalise(adjusted_coefficients) | |
| # 6. Estimate missing total energy | |
| missing_keys = annual_coefficients.keys - customer_profile.keys | |
| missing_fraction = missing_keys.sum { |ts| renormalised[ts] } | |
| estimated_annual_total = observed_total / (1 - missing_fraction) | |
| # 7. Fill gaps | |
| filled = annual_coefficients.each_with_object({}) do |(ts, _), h| | |
| if customer_profile.key?(ts) | |
| h[ts] = customer_profile[ts] | |
| else | |
| h[ts] = estimated_annual_total * renormalised[ts] | |
| end | |
| end | |
| filled | |
| end | |
| def monthly_totals(series) | |
| series.each_with_object(Hash.new(0.0)) do |(ts, value), h| | |
| key = [ts.year, ts.month] | |
| h[key] += value | |
| end | |
| end | |
| def compute_monthly_bias(national_monthly:, customer_monthly:, observed_total:) | |
| national_observed_monthly = national_monthly.select do |(year, month), _| | |
| customer_monthly.key?([year, month]) | |
| end | |
| national_observed_total = national_observed_monthly.values.sum | |
| bias = {} | |
| national_monthly.each do |(year, month), national_month_total| | |
| if customer_monthly.key?([year, month]) | |
| customer_fraction = | |
| customer_monthly[[year, month]] / observed_total | |
| national_fraction = | |
| national_month_total / national_observed_total | |
| raw_bias = customer_fraction / national_fraction | |
| # Clamp to avoid extreme distortion | |
| bias[[year, month]] = clamp(raw_bias, 0.5, 2.0) | |
| else | |
| bias[[year, month]] = 1.0 | |
| end | |
| end | |
| bias | |
| end | |
| def apply_monthly_bias(annual:, monthly_bias:) | |
| annual.transform_values.with_index do |value, idx| | |
| ts = annual.keys[idx] | |
| factor = monthly_bias[[ts.year, ts.month]] || 1.0 | |
| value * factor | |
| end | |
| end | |
| def renormalise(series) | |
| total = series.values.sum | |
| series.transform_values { |v| v / total } | |
| end | |
| def clamp(value, min, max) | |
| [[value, min].max, max].min | |
| end | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment