When EnergyProject.generation is called, DCAF builds the generation stream across various dates. generation provides frequency and timing args intended to allow the user to configure the booking dates. The booking dates are based on calendar years, however, not on window dates. To illustrate, consider a generation across the period 12/31/26 to 12/31/27, with frequency="year" and timing="end". Instead of booking the generation on 12/31/27 (the end of the window), it books the generation on 12/31/26, since that date is the end of the calendar year in which the generation period starts. This feels unintuitive and uncustomary, and it has a significant downstream effect on project economics.
Evidence of this issue is shown here, where the booking date for a generation event is determined:
|
delta = time_delta_per_period(frequency) |
|
current = start |
|
schedule = [] |
|
while current < exclusive_end: |
|
window_end = min(current + delta, exclusive_end) |
|
schedule.append( |
|
ScheduledPeriod( |
|
start=current, |
|
end=window_end, |
|
event_date=event_date( |
|
current, frequency, timing, phase_start, effective_phase_end |
|
), |
|
fraction=elapsed_periods( |
|
current, |
|
window_end, |
|
frequency, |
|
self.config.day_count_convention, |
|
), |
|
) |
|
) |
|
current += delta |
current is set to start, then is passed in as dt to event_date, which handles it thus:
|
match timing: |
|
case "end": |
|
cal_end = period_end(dt, frequency) |
|
return min(cal_end, phase_end) if phase_end is not None else cal_end |
This calculates the end date of the calendar year for dt. Since dt is the start of the generation period, the strange behavior is introduced.
There are two main strategies for addressing this issue. First, generation periods could reflect calendar years. In this scenario, the generation for the first and last partial years would be prorated based on the date. Second, generation periods could remain as mid-year-to-mid-year windows, but the entire generation could be booked based on the period window, not the calendar date. Thus, in the example above, a timing of "end" would place the first generation event on 12/31/27, which would cover all generation from 12/31/26 (inclusive) to 12/31/27 (exclusive).
When
EnergyProject.generationis called, DCAF builds the generation stream across various dates.generationprovidesfrequencyandtimingargs intended to allow the user to configure the booking dates. The booking dates are based on calendar years, however, not on window dates. To illustrate, consider a generation across the period 12/31/26 to 12/31/27, withfrequency="year"andtiming="end". Instead of booking the generation on 12/31/27 (the end of the window), it books the generation on 12/31/26, since that date is the end of the calendar year in which the generation period starts. This feels unintuitive and uncustomary, and it has a significant downstream effect on project economics.Evidence of this issue is shown here, where the booking date for a generation event is determined:
DCAF/dcaf/project/_compiler.py
Lines 1486 to 1506 in 801234a
currentis set tostart, then is passed in asdttoevent_date, which handles it thus:DCAF/dcaf/shared/time.py
Lines 171 to 174 in 801234a
This calculates the end date of the calendar year for
dt. Sincedtis the start of the generation period, the strange behavior is introduced.There are two main strategies for addressing this issue. First, generation periods could reflect calendar years. In this scenario, the generation for the first and last partial years would be prorated based on the date. Second, generation periods could remain as mid-year-to-mid-year windows, but the entire generation could be booked based on the period window, not the calendar date. Thus, in the example above, a timing of "end" would place the first generation event on 12/31/27, which would cover all generation from 12/31/26 (inclusive) to 12/31/27 (exclusive).