flextool 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flextool/__init__.py +41 -0
- flextool/_mem_sampler.py +193 -0
- flextool/_resources.py +43 -0
- flextool/calibrate/__init__.py +51 -0
- flextool/calibrate/__main__.py +11 -0
- flextool/calibrate/_cli.py +316 -0
- flextool/calibrate/_db_alt.py +166 -0
- flextool/calibrate/_final_outputs.py +110 -0
- flextool/calibrate/_guard.py +151 -0
- flextool/calibrate/_loop.py +558 -0
- flextool/calibrate/_readers.py +223 -0
- flextool/calibrate/_report.py +263 -0
- flextool/calibrate/_sizing.py +699 -0
- flextool/calibrate/_solve.py +134 -0
- flextool/calibrate/_solve_status.py +495 -0
- flextool/cli/__init__.py +9 -0
- flextool/cli/_console.py +51 -0
- flextool/cli/_timing.py +147 -0
- flextool/cli/cmd_execute_flextool_workflow.py +187 -0
- flextool/cli/cmd_export_to_tabular.py +56 -0
- flextool/cli/cmd_import_sensitivities.py +75 -0
- flextool/cli/cmd_migrate_database.py +13 -0
- flextool/cli/cmd_open_results_db.py +269 -0
- flextool/cli/cmd_read_matpower.py +66 -0
- flextool/cli/cmd_read_old_flextool.py +63 -0
- flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
- flextool/cli/cmd_read_tabular_input.py +81 -0
- flextool/cli/cmd_run_flextool.py +1095 -0
- flextool/cli/cmd_scenario_results.py +284 -0
- flextool/cli/cmd_solve_mps.py +169 -0
- flextool/cli/cmd_update_flextool.py +17 -0
- flextool/cli/cmd_write_outputs.py +125 -0
- flextool/common_utils/__init__.py +1 -0
- flextool/common_utils/plot_mem_shape.py +77 -0
- flextool/common_utils/precision.py +451 -0
- flextool/decomposition/__init__.py +0 -0
- flextool/decomposition/region_decomposition.py +128 -0
- flextool/decomposition/region_filter.py +1261 -0
- flextool/engine_polars/__init__.py +110 -0
- flextool/engine_polars/_axis_enums.py +742 -0
- flextool/engine_polars/_benders.py +3462 -0
- flextool/engine_polars/_block_layout.py +1479 -0
- flextool/engine_polars/_blocks.py +1515 -0
- flextool/engine_polars/_commodity_ladder.py +660 -0
- flextool/engine_polars/_cumulative_invest.py +1165 -0
- flextool/engine_polars/_db_loader.py +153 -0
- flextool/engine_polars/_db_reader.py +127 -0
- flextool/engine_polars/_dc_power_flow.py +445 -0
- flextool/engine_polars/_delay.py +442 -0
- flextool/engine_polars/_derived_arithmetic.py +432 -0
- flextool/engine_polars/_derived_block.py +990 -0
- flextool/engine_polars/_derived_branch.py +769 -0
- flextool/engine_polars/_derived_existing.py +1353 -0
- flextool/engine_polars/_derived_npv.py +1297 -0
- flextool/engine_polars/_derived_params.py +9850 -0
- flextool/engine_polars/_derived_profile.py +881 -0
- flextool/engine_polars/_derived_walks.py +276 -0
- flextool/engine_polars/_determinism.py +70 -0
- flextool/engine_polars/_direct_params.py +2186 -0
- flextool/engine_polars/_dump_csvs.py +1009 -0
- flextool/engine_polars/_emit_arc_unions.py +1631 -0
- flextool/engine_polars/_emit_calc_params.py +729 -0
- flextool/engine_polars/_emit_chain_params.py +709 -0
- flextool/engine_polars/_emit_co2_accumulators.py +400 -0
- flextool/engine_polars/_emit_dispatchers.py +690 -0
- flextool/engine_polars/_emit_energy_margin.py +125 -0
- flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
- flextool/engine_polars/_emit_entity_annual.py +428 -0
- flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
- flextool/engine_polars/_emit_leaf_sets.py +550 -0
- flextool/engine_polars/_emit_lp_scaling.py +665 -0
- flextool/engine_polars/_emit_mid_sets.py +859 -0
- flextool/engine_polars/_emit_pdt_params.py +759 -0
- flextool/engine_polars/_emit_per_solve.py +774 -0
- flextool/engine_polars/_emit_period_calc.py +504 -0
- flextool/engine_polars/_emit_period_params.py +2398 -0
- flextool/engine_polars/_emit_provider_io.py +141 -0
- flextool/engine_polars/_emit_reserve.py +574 -0
- flextool/engine_polars/_emit_solve_time.py +311 -0
- flextool/engine_polars/_emit_solve_writers.py +1249 -0
- flextool/engine_polars/_flex_data_accumulator.py +388 -0
- flextool/engine_polars/_flex_data_provider.py +478 -0
- flextool/engine_polars/_group_slack.py +1253 -0
- flextool/engine_polars/_inmemory_reader.py +140 -0
- flextool/engine_polars/_input_source.py +336 -0
- flextool/engine_polars/_invest_seeds.py +191 -0
- flextool/engine_polars/_native_input_writer.py +100 -0
- flextool/engine_polars/_native_run_model.py +1348 -0
- flextool/engine_polars/_orchestration.py +4314 -0
- flextool/engine_polars/_output_writer.py +439 -0
- flextool/engine_polars/_param_shapes.py +1595 -0
- flextool/engine_polars/_parquet_bundle.py +723 -0
- flextool/engine_polars/_pdt_join.py +167 -0
- flextool/engine_polars/_pdt_lookup.py +547 -0
- flextool/engine_polars/_per_solve_sets.py +335 -0
- flextool/engine_polars/_projection_params.py +2056 -0
- flextool/engine_polars/_provider_keys.py +173 -0
- flextool/engine_polars/_provider_translators.py +225 -0
- flextool/engine_polars/_recursive_solve.py +703 -0
- flextool/engine_polars/_region_filter.py +2508 -0
- flextool/engine_polars/_reserve.py +649 -0
- flextool/engine_polars/_solve_acceptance.py +331 -0
- flextool/engine_polars/_solve_config.py +1001 -0
- flextool/engine_polars/_solve_context.py +885 -0
- flextool/engine_polars/_solve_handoff.py +164 -0
- flextool/engine_polars/_solve_state.py +232 -0
- flextool/engine_polars/_solver_base.py +36 -0
- flextool/engine_polars/_solver_dispatch.py +511 -0
- flextool/engine_polars/_spinedb_reader.py +1165 -0
- flextool/engine_polars/_stochastic.py +593 -0
- flextool/engine_polars/_subprocess_solve.py +1838 -0
- flextool/engine_polars/_timeline.py +1416 -0
- flextool/engine_polars/_vectorize.py +438 -0
- flextool/engine_polars/_warm.py +858 -0
- flextool/engine_polars/autoscale/__init__.py +107 -0
- flextool/engine_polars/autoscale/_config.py +218 -0
- flextool/engine_polars/autoscale/_layer2.py +1253 -0
- flextool/engine_polars/autoscale/_layer2_types.py +584 -0
- flextool/engine_polars/autoscale/_quantity_types.py +621 -0
- flextool/engine_polars/autoscale/_report.py +336 -0
- flextool/engine_polars/chain.py +259 -0
- flextool/engine_polars/input.py +6638 -0
- flextool/engine_polars/model.py +4754 -0
- flextool/env_check.py +388 -0
- flextool/export_to_tabular/__init__.py +5 -0
- flextool/export_to_tabular/db_reader.py +224 -0
- flextool/export_to_tabular/excel_writer.py +3559 -0
- flextool/export_to_tabular/export_settings.yaml +377 -0
- flextool/export_to_tabular/export_to_excel.py +227 -0
- flextool/export_to_tabular/formatting.py +543 -0
- flextool/export_to_tabular/sheet_config.py +876 -0
- flextool/gui/__init__.py +0 -0
- flextool/gui/__main__.py +118 -0
- flextool/gui/calibrate_commands.py +184 -0
- flextool/gui/calibrate_jobs.py +424 -0
- flextool/gui/check_tree.py +142 -0
- flextool/gui/cli_format.py +83 -0
- flextool/gui/config_parser.py +68 -0
- flextool/gui/data_models.py +362 -0
- flextool/gui/db_editor_integration.py +202 -0
- flextool/gui/db_version_check.py +269 -0
- flextool/gui/dialogs/__init__.py +0 -0
- flextool/gui/dialogs/add_dialog.py +1098 -0
- flextool/gui/dialogs/calibrate_dialog.py +1259 -0
- flextool/gui/dialogs/file_picker.py +473 -0
- flextool/gui/dialogs/group_picker.py +299 -0
- flextool/gui/dialogs/migration_consent_dialog.py +106 -0
- flextool/gui/dialogs/migration_progress_dialog.py +237 -0
- flextool/gui/dialogs/plot_dialog.py +459 -0
- flextool/gui/dialogs/plot_settings_picker.py +2184 -0
- flextool/gui/dialogs/project_dialog.py +426 -0
- flextool/gui/dialogs/update_dialog.py +212 -0
- flextool/gui/downsampling.py +88 -0
- flextool/gui/error_handling.py +50 -0
- flextool/gui/execution_manager.py +1715 -0
- flextool/gui/execution_window.py +1377 -0
- flextool/gui/hover_tooltip.py +111 -0
- flextool/gui/input_sources.py +730 -0
- flextool/gui/main_window.py +6181 -0
- flextool/gui/network_graph.py +215 -0
- flextool/gui/output_actions.py +393 -0
- flextool/gui/output_log_window.py +159 -0
- flextool/gui/platform_utils.py +421 -0
- flextool/gui/plot_cache.py +88 -0
- flextool/gui/plot_canvas.py +543 -0
- flextool/gui/plot_config_reader.py +272 -0
- flextool/gui/project_utils.py +100 -0
- flextool/gui/result_viewer.py +4394 -0
- flextool/gui/scenario_key.py +162 -0
- flextool/gui/scenario_lists.py +516 -0
- flextool/gui/settings_io.py +360 -0
- flextool/gui/solve_reader.py +103 -0
- flextool/gui/tree_reorder.py +88 -0
- flextool/gui/ui_metrics.py +420 -0
- flextool/input_derivation/__init__.py +281 -0
- flextool/input_derivation/_commodity_ladder.py +375 -0
- flextool/input_derivation/_commodity_ladder_sets.py +70 -0
- flextool/input_derivation/_dc_power_flow.py +377 -0
- flextool/input_derivation/_method_constants.py +77 -0
- flextool/input_derivation/_process_method.py +258 -0
- flextool/input_derivation/_specs.py +1026 -0
- flextool/input_derivation/_validators.py +321 -0
- flextool/lean_parquet.py +159 -0
- flextool/model_builder/__init__.py +5 -0
- flextool/model_builder/build_model.py +589 -0
- flextool/model_builder/encoding.py +67 -0
- flextool/model_builder/names.py +34 -0
- flextool/model_builder/profiles.py +129 -0
- flextool/plot_outputs/__init__.py +14 -0
- flextool/plot_outputs/axis_helpers.py +355 -0
- flextool/plot_outputs/color_template.py +888 -0
- flextool/plot_outputs/config.py +171 -0
- flextool/plot_outputs/format_helpers.py +345 -0
- flextool/plot_outputs/legend_helpers.py +143 -0
- flextool/plot_outputs/orchestrator.py +1141 -0
- flextool/plot_outputs/perf.py +37 -0
- flextool/plot_outputs/plan.py +1787 -0
- flextool/plot_outputs/plot_bars.py +1510 -0
- flextool/plot_outputs/plot_bars_detail.py +753 -0
- flextool/plot_outputs/plot_lines.py +951 -0
- flextool/plot_outputs/shared_manifest.py +564 -0
- flextool/plot_outputs/subplot_helpers.py +137 -0
- flextool/process_inputs/__init__.py +188 -0
- flextool/process_inputs/import_old_excel_input.json +4159 -0
- flextool/process_inputs/read_matpower.py +451 -0
- flextool/process_inputs/read_old_flextool.py +1288 -0
- flextool/process_inputs/read_self_describing_excel.py +1423 -0
- flextool/process_inputs/read_tabular_with_specification.py +1114 -0
- flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
- flextool/process_inputs/write_self_describing_to_db.py +977 -0
- flextool/process_inputs/write_to_input_db.py +269 -0
- flextool/process_outputs/__init__.py +7 -0
- flextool/process_outputs/_annualize.py +55 -0
- flextool/process_outputs/_inmemory_helpers.py +292 -0
- flextool/process_outputs/_output_meta.py +672 -0
- flextool/process_outputs/calc_capacity_flows.py +107 -0
- flextool/process_outputs/calc_connections.py +136 -0
- flextool/process_outputs/calc_costs.py +260 -0
- flextool/process_outputs/calc_group_flows.py +192 -0
- flextool/process_outputs/calc_slacks.py +103 -0
- flextool/process_outputs/calc_storage_vre.py +160 -0
- flextool/process_outputs/drop_levels.py +208 -0
- flextool/process_outputs/handoff_writers.py +1315 -0
- flextool/process_outputs/out_ancillary.py +544 -0
- flextool/process_outputs/out_capacity.py +179 -0
- flextool/process_outputs/out_costs.py +334 -0
- flextool/process_outputs/out_flowgroup.py +189 -0
- flextool/process_outputs/out_flows.py +301 -0
- flextool/process_outputs/out_group.py +475 -0
- flextool/process_outputs/out_node.py +190 -0
- flextool/process_outputs/persist_realized_slice.py +601 -0
- flextool/process_outputs/process_results.py +24 -0
- flextool/process_outputs/read_highs_solution.py +2256 -0
- flextool/process_outputs/read_parameters.py +1799 -0
- flextool/process_outputs/read_sets.py +1095 -0
- flextool/process_outputs/read_variables.py +553 -0
- flextool/process_outputs/solve_order.py +81 -0
- flextool/process_outputs/spinedb_replay.py +412 -0
- flextool/process_outputs/union_realized_slice.py +224 -0
- flextool/process_outputs/write_outputs.py +1286 -0
- flextool/process_outputs/write_spinedb.py +1267 -0
- flextool/representative_periods/__init__.py +5 -0
- flextool/representative_periods/clustering.py +165 -0
- flextool/representative_periods/force_include.py +563 -0
- flextool/representative_periods/netload.py +365 -0
- flextool/representative_periods/netload_inputs.py +345 -0
- flextool/representative_periods/netload_iterate.py +722 -0
- flextool/representative_periods/preprocess.py +948 -0
- flextool/representative_periods/scenario_stack.py +195 -0
- flextool/representative_periods/weights.py +124 -0
- flextool/scenario_comparison/__init__.py +13 -0
- flextool/scenario_comparison/config_builder.py +158 -0
- flextool/scenario_comparison/constants.py +20 -0
- flextool/scenario_comparison/data_models.py +222 -0
- flextool/scenario_comparison/db_reader.py +399 -0
- flextool/scenario_comparison/dispatch_data.py +1002 -0
- flextool/scenario_comparison/dispatch_mappings.py +205 -0
- flextool/scenario_comparison/dispatch_plots.py +691 -0
- flextool/scenario_comparison/input_entity_colors.py +319 -0
- flextool/scenario_comparison/orchestrator.py +453 -0
- flextool/scenario_comparison/plan_union.py +244 -0
- flextool/scenario_comparison/plot_settings_seed.py +205 -0
- flextool/schemas/AXIS_CONTRACT.md +71 -0
- flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
- flextool/schemas/canonical_databases/howto_connections.json +5606 -0
- flextool/schemas/canonical_databases/howto_demand.json +5518 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
- flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
- flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
- flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
- flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
- flextool/schemas/canonical_databases/templates_examples.json +13532 -0
- flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
- flextool/schemas/comparison_settings_template.json +197 -0
- flextool/schemas/default_plot_settings.yaml +260 -0
- flextool/schemas/default_plots.yaml +2293 -0
- flextool/schemas/flextool_axis_contract.json +303 -0
- flextool/schemas/flextool_axis_contract.schema.json +247 -0
- flextool/schemas/old_flextool_import_template.json +4443 -0
- flextool/schemas/output_info_template.json +48 -0
- flextool/schemas/output_settings_template.json +256 -0
- flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
- flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
- flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
- flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
- flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
- flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
- flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
- flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
- flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
- flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
- flextool/schemas/spinedb_results_schema.json +581 -0
- flextool/schemas/spinedb_schema.json +4636 -0
- flextool/solver_config/copt.opt.template +18 -0
- flextool/solver_config/cplex.opt.template +25 -0
- flextool/solver_config/gurobi.opt.template +18 -0
- flextool/solver_config/highs.opt.template +18 -0
- flextool/solver_config/xpress.opt.template +26 -0
- flextool/spinedb_backend/__init__.py +26 -0
- flextool/spinedb_backend/_axis_enums.py +1119 -0
- flextool/spinedb_backend/_backend.py +1139 -0
- flextool/update_flextool/__init__.py +12 -0
- flextool/update_flextool/canonical_databases.py +251 -0
- flextool/update_flextool/db_migration.py +7108 -0
- flextool/update_flextool/ensure_settings_db.py +138 -0
- flextool/update_flextool/export_database.py +103 -0
- flextool/update_flextool/extend_tests_fixture.py +772 -0
- flextool/update_flextool/generate_canonical.py +274 -0
- flextool/update_flextool/initialize_database.py +42 -0
- flextool/update_flextool/install_info.py +225 -0
- flextool/update_flextool/self_update.py +464 -0
- flextool/update_flextool/sync_master_json_template.py +125 -0
- flextool/update_flextool/test_fixtures.py +187 -0
- flextool-4.0.0.dist-info/METADATA +217 -0
- flextool-4.0.0.dist-info/RECORD +322 -0
- flextool-4.0.0.dist-info/WHEEL +5 -0
- flextool-4.0.0.dist-info/entry_points.txt +17 -0
- flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
- flextool-4.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Load and combine dispatch mapping parquet files across scenarios.
|
|
2
|
+
|
|
3
|
+
Exports:
|
|
4
|
+
load_dispatch_mappings — load all mapping files from one scenario folder
|
|
5
|
+
combine_dispatch_mappings — combine across scenarios → DispatchMappings
|
|
6
|
+
get_group_node_multiindex — build (group, node) MultiIndex for a scenario
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
|
|
15
|
+
from flextool.lean_parquet import read_lean_parquet
|
|
16
|
+
from flextool.scenario_comparison.data_models import DispatchMappings
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_dispatch_mappings(parquet_dir: Path) -> dict[str, pd.DataFrame | None]:
|
|
20
|
+
"""
|
|
21
|
+
Load all dispatch-related mapping files from a single parquet directory.
|
|
22
|
+
|
|
23
|
+
Parameters:
|
|
24
|
+
-----------
|
|
25
|
+
parquet_dir : Path
|
|
26
|
+
Directory containing the parquet files for one scenario
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
--------
|
|
30
|
+
dict : Dictionary containing all dispatch mapping dataframes (raw, unfiltered)
|
|
31
|
+
"""
|
|
32
|
+
parquet_dir = Path(parquet_dir)
|
|
33
|
+
mappings: dict[str, pd.DataFrame | None] = {}
|
|
34
|
+
|
|
35
|
+
# File mapping: key -> (filename, required)
|
|
36
|
+
# node_inflow is excluded — it's already in combined_dfs as 'node_inflow__dt'
|
|
37
|
+
# ``dispatch_groups`` lists the groups that have on-disk dispatch tables.
|
|
38
|
+
# The model has two separate sets — nodeGroupIndicators (summary
|
|
39
|
+
# indicators) and nodeGroupDispatch (dispatch table output) — and a
|
|
40
|
+
# project may flag either independently. We use nodeGroupDispatch
|
|
41
|
+
# because that's the set that gates the actual time-series files
|
|
42
|
+
# the dispatch view consumes.
|
|
43
|
+
file_mapping = {
|
|
44
|
+
'dispatch_groups': ('nodeGroupDispatch.parquet', True),
|
|
45
|
+
'group_node': ('group_node.parquet', True),
|
|
46
|
+
'group_process_node': ('group_process_node.parquet', False),
|
|
47
|
+
# ProcessGroup aggregation files
|
|
48
|
+
'processGroup_Unit_to_group': ('nodeGroupDispatch__processGroup_Unit_to_group.parquet', False),
|
|
49
|
+
'processGroup_Group_to_unit': ('nodeGroupDispatch__processGroup_Group_to_unit.parquet', False),
|
|
50
|
+
'processGroup_Connection': ('nodeGroupDispatch__processGroup_Connection.parquet', False),
|
|
51
|
+
# ProcessGroup member files
|
|
52
|
+
'processGroup_unit_to_node_members': ('nodeGroupDispatch__processGroup__process__unit__to_node.parquet', False),
|
|
53
|
+
'processGroup_node_to_unit_members': ('nodeGroupDispatch__processGroup__process__node__to_unit.parquet', False),
|
|
54
|
+
'processGroup_connection_to_node_members': ('nodeGroupDispatch__processGroup__process__connection__to_node.parquet', False),
|
|
55
|
+
'processGroup_node_to_connection_members': ('nodeGroupDispatch__processGroup__process__node__to_connection.parquet', False),
|
|
56
|
+
# Not_in_aggregate files
|
|
57
|
+
'not_in_aggregate_unit_to_node': ('nodeGroupDispatch__process__unit__to_node_Not_in_aggregate.parquet', False),
|
|
58
|
+
'not_in_aggregate_node_to_unit': ('nodeGroupDispatch__process__node__to_unit_Not_in_aggregate.parquet', False),
|
|
59
|
+
'not_in_aggregate_connection_to_node': ('nodeGroupDispatch__process__connection__to_node_Not_in_aggregate.parquet', False),
|
|
60
|
+
'not_in_aggregate_node_to_connection': ('nodeGroupDispatch__process__node__to_connection_Not_in_aggregate.parquet', False),
|
|
61
|
+
'not_in_aggregate_connection': ('nodeGroupDispatch__connection_Not_in_aggregate.parquet', False),
|
|
62
|
+
# Process fully inside (for internal losses)
|
|
63
|
+
'process_fully_inside': ('nodeGroupDispatch__process_fully_inside.parquet', False),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for key, (filename, required) in file_mapping.items():
|
|
67
|
+
filepath = parquet_dir / filename
|
|
68
|
+
if filepath.exists():
|
|
69
|
+
mappings[key] = read_lean_parquet(filepath)
|
|
70
|
+
elif required:
|
|
71
|
+
print(f"Warning: Required file {filepath} not found")
|
|
72
|
+
mappings[key] = None
|
|
73
|
+
else:
|
|
74
|
+
mappings[key] = None
|
|
75
|
+
|
|
76
|
+
# nodeGroupDispatch.parquet stores its single membership column under the
|
|
77
|
+
# set name 'nodeGroupDispatch'; downstream consumers expect 'group'
|
|
78
|
+
# (matching group_node / group_process_node).
|
|
79
|
+
dg_df = mappings.get('dispatch_groups')
|
|
80
|
+
if dg_df is not None and 'nodeGroupDispatch' in dg_df.columns:
|
|
81
|
+
mappings['dispatch_groups'] = dg_df.rename(columns={'nodeGroupDispatch': 'group'})
|
|
82
|
+
|
|
83
|
+
return mappings
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def combine_dispatch_mappings(
|
|
87
|
+
scenario_folders: dict[str, str],
|
|
88
|
+
parquet_subdir: str,
|
|
89
|
+
) -> DispatchMappings:
|
|
90
|
+
"""
|
|
91
|
+
Load and combine dispatch mapping files across all scenarios.
|
|
92
|
+
|
|
93
|
+
Each mapping key's DataFrames are concatenated and indexed by 'scenario'.
|
|
94
|
+
Use ``DispatchMappings.get_for_scenario(field, scenario)`` for per-scenario views.
|
|
95
|
+
|
|
96
|
+
Parameters:
|
|
97
|
+
-----------
|
|
98
|
+
scenario_folders : dict
|
|
99
|
+
Mapping of scenario names to folder paths
|
|
100
|
+
parquet_subdir : str
|
|
101
|
+
Subdirectory within each folder containing parquet files
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
--------
|
|
105
|
+
DispatchMappings : Combined mapping dataclass with 'scenario' in the index
|
|
106
|
+
"""
|
|
107
|
+
# Collect per-key lists: {key: [df, df, ...]}
|
|
108
|
+
collected: dict[str, list[pd.DataFrame]] = {}
|
|
109
|
+
|
|
110
|
+
for scen_name, folder_path in scenario_folders.items():
|
|
111
|
+
pq_dir = Path(folder_path) / parquet_subdir / scen_name
|
|
112
|
+
mappings = load_dispatch_mappings(pq_dir)
|
|
113
|
+
for key, df in mappings.items():
|
|
114
|
+
if df is None or df.empty:
|
|
115
|
+
continue
|
|
116
|
+
# Re-tag the scenario column to the folder identity (the loop
|
|
117
|
+
# key) so two folders holding the same model scenario stay
|
|
118
|
+
# distinct. No-op when the baked-in tag already equals the
|
|
119
|
+
# folder. Mirrors combine_parquet_files; see
|
|
120
|
+
# specs/dispatch_scenario_identity_retag.md.
|
|
121
|
+
if 'scenario' in df.columns:
|
|
122
|
+
df = df.assign(scenario=scen_name)
|
|
123
|
+
if key not in collected:
|
|
124
|
+
collected[key] = []
|
|
125
|
+
collected[key].append(df)
|
|
126
|
+
|
|
127
|
+
# Concatenate and set scenario as index
|
|
128
|
+
combined: dict[str, pd.DataFrame] = {}
|
|
129
|
+
for key, dfs in collected.items():
|
|
130
|
+
combined_df = pd.concat(dfs, ignore_index=True)
|
|
131
|
+
if 'scenario' in combined_df.columns:
|
|
132
|
+
combined_df = combined_df.set_index('scenario')
|
|
133
|
+
combined[key] = combined_df
|
|
134
|
+
|
|
135
|
+
return DispatchMappings(**combined)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def data_scenario_tags(mappings: DispatchMappings) -> list[str]:
|
|
139
|
+
"""Return the scenario identities present in the combined dispatch data.
|
|
140
|
+
|
|
141
|
+
``combine_dispatch_mappings`` re-tags each scenario to its folder
|
|
142
|
+
identity at load time (see
|
|
143
|
+
specs/dispatch_scenario_identity_retag.md), so the values returned here
|
|
144
|
+
are the folder names — already the canonical dispatch slice key. This
|
|
145
|
+
reads them back from the data (e.g. so a caller can enumerate exactly
|
|
146
|
+
the folders that carry dispatch groups, skipping ones that don't).
|
|
147
|
+
|
|
148
|
+
Identities are returned first-seen-ordered (mirroring the folder order
|
|
149
|
+
that ``combine_dispatch_mappings`` concatenated), de-duplicated.
|
|
150
|
+
Returns ``[]`` when the data carries no scenario index/column.
|
|
151
|
+
"""
|
|
152
|
+
df = mappings.dispatch_groups
|
|
153
|
+
if df is None or df.empty:
|
|
154
|
+
return []
|
|
155
|
+
if df.index.name == 'scenario':
|
|
156
|
+
return list(dict.fromkeys(df.index.tolist()))
|
|
157
|
+
if 'scenario' in df.columns:
|
|
158
|
+
return list(dict.fromkeys(df['scenario'].tolist()))
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def resolve_data_scenario_tag(mappings: DispatchMappings, fallback: str) -> str:
|
|
163
|
+
"""Return the single scenario identity present in the dispatch data.
|
|
164
|
+
|
|
165
|
+
Convenience wrapper over :func:`data_scenario_tags` for the
|
|
166
|
+
single-scenario case (post re-tag this is the folder identity).
|
|
167
|
+
Returns *fallback* when the identity is absent or not unambiguously
|
|
168
|
+
single-valued (e.g. combined multi-scenario mappings — use
|
|
169
|
+
:func:`data_scenario_tags` there instead).
|
|
170
|
+
"""
|
|
171
|
+
tags = data_scenario_tags(mappings)
|
|
172
|
+
if len(tags) == 1:
|
|
173
|
+
return tags[0]
|
|
174
|
+
return fallback
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_group_node_multiindex(
|
|
178
|
+
group_node_df: pd.DataFrame | None,
|
|
179
|
+
scenario: str | None = None,
|
|
180
|
+
) -> pd.MultiIndex | None:
|
|
181
|
+
"""
|
|
182
|
+
Create a MultiIndex from group_node dataframe for a specific scenario.
|
|
183
|
+
|
|
184
|
+
Parameters:
|
|
185
|
+
-----------
|
|
186
|
+
group_node_df : pd.DataFrame
|
|
187
|
+
DataFrame with columns [scenario, group, node]
|
|
188
|
+
scenario : str, optional
|
|
189
|
+
Scenario name to filter by. If None, uses all rows.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
--------
|
|
193
|
+
pd.MultiIndex : MultiIndex with levels [group, node]
|
|
194
|
+
"""
|
|
195
|
+
if group_node_df is None:
|
|
196
|
+
return None
|
|
197
|
+
if scenario is None:
|
|
198
|
+
df = group_node_df
|
|
199
|
+
elif 'scenario' in group_node_df.columns:
|
|
200
|
+
df = group_node_df[group_node_df['scenario'] == scenario]
|
|
201
|
+
elif group_node_df.index.name == 'scenario':
|
|
202
|
+
df = group_node_df.xs(scenario)
|
|
203
|
+
else:
|
|
204
|
+
df = group_node_df
|
|
205
|
+
return pd.MultiIndex.from_frame(df[['group', 'node']])
|