Skip to content

Mb/sources#75

Merged
jd-lara merged 5 commits into
mainfrom
mb/sources
May 29, 2026
Merged

Mb/sources#75
jd-lara merged 5 commits into
mainfrom
mb/sources

Conversation

@m-bossart

Copy link
Copy Markdown
Contributor

Adds capability for processing sources as part of the generation data.
I've tested this with EI sims. We need to eventually add more comprehensive testing to PowerAnalytics to capture more realistic sets of results.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for treating PSY.Source components as part of “generation” results processing, enabling sources to be included in key selection, fixed-parameter promotion, and fuel/category aggregation.

Changes:

  • Add Source-specific variable/parameter key filtering and a sources toggle in get_generation_data.
  • Update categorization logic to handle split-power time-series parameters (ActivePower(In|Out)TimeSeriesParameter) in addition to variables.
  • Extend fuel categorization + YAML mapping to include Source components.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/get_data.jl Adds Source key collection, improves fixed-parameter handling for multiple Source params, and extends categorization for split-power time-series parameters.
src/fuel_results.jl Includes PSY.Source components in make_fuel_dictionary so they participate in fuel/category aggregation.
src/definitions.jl Defines supported Source variable/parameter entry types.
deps/generator_mapping.yaml Adds a Source category mapping and marks it as a non-generator in metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/get_data.jl
Comment on lines 360 to +396
function get_generation_data(
results::R;
# aggregation::Union{
# Type{PSY.StaticInjection},
# Type{PSY.ACBus},
# Type{PSY.System},
# Type{<:PSY.AggregationTopology},
# } = PSY.StaticInjection,
filter_func::Union{Function, Nothing} = nothing,
kwargs...,
) where {R <: IS.Results}
initial_time = get(kwargs, :initial_time, get(kwargs, :start_time, nothing))
len = get(kwargs, :horizon, get(kwargs, :len, nothing))
variable_keys = get(kwargs, :variable_keys, PSI.list_variable_keys(results))
parameter_keys = get(kwargs, :parameter_keys, PSI.list_parameter_keys(results))
aux_variable_keys = get(kwargs, :aux_variable_keys, PSI.list_aux_variable_keys(results))
curtailment = get(kwargs, :curtailment, true)
storage = get(kwargs, :storage, true)
sources = get(kwargs, :sources, true)

if curtailment && (haskey(kwargs, :variable_keys) || haskey(kwargs, :parameter_keys))
@warn "Cannot guarantee curtailment calculations with specified keys"
end

injection_keys = get_generation_variable_keys(results; variable_keys = variable_keys)
if storage
injection_keys = vcat(
injection_keys,
get_storage_variable_keys(results; variable_keys = variable_keys),
)
end
if sources
injection_keys = vcat(
injection_keys,
get_source_variable_keys(results; variable_keys = variable_keys),
)
end
Comment thread src/get_data.jl
Comment on lines 669 to +742
var_types = Dict{String, Symbol}()
for k in keys(data)
keystring = string(k)
device_type_string = last(split(keystring, "__"))
if occursin("ActivePowerInVariable", keystring) ||
occursin("ActivePowerOutVariable", keystring)
occursin("ActivePowerOutVariable", keystring) ||
occursin("ActivePowerInTimeSeriesParameter", keystring) ||
occursin("ActivePowerOutTimeSeriesParameter", keystring)
push!(split_power_component_types, device_type_string)
continue
end
var_types[device_type_string] = k
end

# Categories that contain a split-power component type (e.g. storage, which
# reports separate ActivePowerIn/OutVariable) are emitted as "<category> In"
# and "<category> Out" instead of a single combined category. Keep the
# original key type so `aggregation[category]` works even if `aggregation`
# is keyed by something other than `String`; we stringify only at write time.
split_categories = Set{keytype(aggregation)}()
for (category, list) in aggregation
if any(
component_type in split_power_component_types for (component_type, _) in list
)
push!(split_categories, category)
end
end

# Non-split components: one column per component under the original category.
# Split-power components are skipped here and handled by the In/Out pass below.
for (category, list) in aggregation
category_df = DataFrames.DataFrame()
for (component_type, component_name) in list
component_type in split_power_component_types && continue
haskey(var_types, component_type) || continue
category_data = data[var_types[component_type]]
colname =
if typeof(names(category_data)[1]) == String
"$component_name"
else
component_name
end
DataFrames.insertcols!(
category_df,
(colname => category_data[:, colname]);
makeunique = true,
)
end
if !isempty(category_df)
category_dataframes[string(category)] = category_df
end
end

# Split pass: discharging (ActivePowerOutVariable) is generation (+),
# charging (ActivePowerInVariable) is load (-).
# ActivePowerInTimeSeriesParameter is already negative (its multiplier is
# active_power_limits.min, which is negative), so no sign flip is needed for it.
for category in split_categories
list = aggregation[category]
for (suffix, variable_prefix, sign) in (
("Out", "ActivePowerOutVariable", 1.0),
("In", "ActivePowerInVariable", -1.0),
for (suffix, variable_prefix_sign_pairs) in (
(
"Out",
[
("ActivePowerOutVariable", 1.0),
("ActivePowerOutTimeSeriesParameter", 1.0),
],
),
(
"In",
[
("ActivePowerInVariable", -1.0),
("ActivePowerInTimeSeriesParameter", 1.0),
],
),
Comment thread src/fuel_results.jl
Comment on lines +147 to +161
for source in PSY.get_components(PSY.Source, sys)
if !filter_func2(source)
continue
end

ext = get(PSY.get_ext(source), "ext_category", nothing)
category = something(
get_generator_category(typeof(source), nothing, nothing, ext, mapping),
UNMAPPED_GENERATOR_CATEGORY,
)
push!(
get!(gen_categories, "$category", Tuple{String, String}[]),
(string(nameof(typeof(source))), PSY.get_name(source)),
)
end
Copilot AI requested a review from jd-lara May 29, 2026 00:03
@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.92308% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.94%. Comparing base (a1d5c05) to head (0f39a27).
⚠️ Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
src/fuel_results.jl 14.28% 6 Missing ⚠️
src/get_data.jl 90.62% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #75      +/-   ##
==========================================
- Coverage   88.55%   87.94%   -0.62%     
==========================================
  Files           7        7              
  Lines         734      763      +29     
==========================================
+ Hits          650      671      +21     
- Misses         84       92       +8     
Flag Coverage Δ
unittests 87.94% <76.92%> (-0.62%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jd-lara jd-lara merged commit bfdcb7f into main May 29, 2026
5 of 7 checks passed
@jd-lara jd-lara deleted the mb/sources branch June 9, 2026 04:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants