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,543 @@
|
|
|
1
|
+
"""Cell formatting constants and helper functions for the Excel export.
|
|
2
|
+
|
|
3
|
+
Provides fill colours, fonts, and per-layout formatting functions that match
|
|
4
|
+
the existing FlexTool Excel template. All theme colours reference the Office
|
|
5
|
+
default colour theme shipped with openpyxl workbooks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from openpyxl.styles import Font, PatternFill
|
|
11
|
+
from openpyxl.styles.colors import Color
|
|
12
|
+
from openpyxl.utils import get_column_letter
|
|
13
|
+
from openpyxl.worksheet.worksheet import Worksheet
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Colour / font constants
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
# Header row (row 2 in constant/periodic sheets) fills
|
|
20
|
+
FILL_ALT_HEADER = PatternFill(
|
|
21
|
+
patternType="solid", fgColor=Color(theme=9, tint=0.3999755851924192)
|
|
22
|
+
)
|
|
23
|
+
FILL_ENTITY_HEADER = PatternFill(
|
|
24
|
+
patternType="solid", fgColor=Color(theme=7, tint=0.5999938962981048)
|
|
25
|
+
)
|
|
26
|
+
FILL_PARAM_HEADER = PatternFill(
|
|
27
|
+
patternType="solid", fgColor=Color(theme=4, tint=0.5999938962981048)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Data row fills (rows 3+ in constant/periodic sheets)
|
|
31
|
+
FILL_ALT_DATA = PatternFill(
|
|
32
|
+
patternType="solid", fgColor=Color(theme=9, tint=0.5999938962981048)
|
|
33
|
+
)
|
|
34
|
+
FILL_ENTITY_DATA = PatternFill(
|
|
35
|
+
patternType="solid", fgColor=Color(theme=7, tint=0.7999816888943144)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Timeseries-specific fills
|
|
39
|
+
FILL_TIME_HEADER = PatternFill(
|
|
40
|
+
patternType="solid", fgColor=Color(theme=0, tint=-0.249977111117893)
|
|
41
|
+
)
|
|
42
|
+
FILL_TIME_DATA = PatternFill(
|
|
43
|
+
patternType="solid", fgColor=Color(theme=0, tint=-0.1499984740745262)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Description/data-type label cells (row 1-2, definition column) – medium grey, black font
|
|
47
|
+
FILL_DESC_ROW = PatternFill(
|
|
48
|
+
patternType="solid", fgColor=Color(theme=0, tint=-0.3499862666707358)
|
|
49
|
+
)
|
|
50
|
+
FONT_DESC_ROW = Font(color=Color(theme=1, tint=0.0))
|
|
51
|
+
|
|
52
|
+
# Description/data-type data cells (row 1-2, parameter columns) – lighter grey, black font
|
|
53
|
+
FILL_DESC_DATA = PatternFill(
|
|
54
|
+
patternType="solid", fgColor=Color(theme=0, tint=-0.249977111117893)
|
|
55
|
+
)
|
|
56
|
+
FONT_DESC_DATA = Font(color=Color(theme=1, tint=0.0))
|
|
57
|
+
|
|
58
|
+
# Definition column background (darker grey) — conveys "do not fill here"
|
|
59
|
+
FILL_DEF_COL = PatternFill(
|
|
60
|
+
patternType="solid", fgColor=Color(theme=0, tint=-0.3499862666707358)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# 'navigate' label in row 1 of each sheet (blue link colour)
|
|
64
|
+
FONT_NAVIGATE_LINK = Font(color=Color(theme=10, tint=0.0))
|
|
65
|
+
|
|
66
|
+
# Parameter *label* cell (e.g. the word "parameter" in timeseries sheets)
|
|
67
|
+
FILL_PARAM_LABEL = PatternFill(
|
|
68
|
+
patternType="solid", fgColor=Color(theme=4, tint=0.3999755851924192)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# Parameter data columns — slightly darker grey than default white
|
|
72
|
+
FILL_PARAM_DATA = PatternFill(
|
|
73
|
+
patternType="solid", fgColor=Color(theme=0, tint=-0.0499893185216834)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Index dimension columns (period, time, constraint) — light green
|
|
77
|
+
FILL_INDEX_HEADER = PatternFill(
|
|
78
|
+
patternType="solid", fgColor="C6EFCE"
|
|
79
|
+
)
|
|
80
|
+
FILL_INDEX_DATA = PatternFill(
|
|
81
|
+
patternType="solid", fgColor="E2EFDA"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Formatting helpers
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def format_constant_sheet(
|
|
91
|
+
ws: Worksheet,
|
|
92
|
+
n_entity_cols: int,
|
|
93
|
+
n_extra_cols: int = 0,
|
|
94
|
+
has_entity_alt: bool = True,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Apply formatting to a constant-layout sheet.
|
|
97
|
+
|
|
98
|
+
Column layout (1-based):
|
|
99
|
+
1 alternative
|
|
100
|
+
2 .. 1+n_entity_cols entity dimension columns
|
|
101
|
+
next n_extra_cols extra columns (e.g. left_node, right_node)
|
|
102
|
+
remaining parameter columns (+ optional Entity Alternative)
|
|
103
|
+
|
|
104
|
+
Row layout:
|
|
105
|
+
1 description / navigate
|
|
106
|
+
2 headers
|
|
107
|
+
3+ data
|
|
108
|
+
"""
|
|
109
|
+
max_row = ws.max_row
|
|
110
|
+
max_col = ws.max_column
|
|
111
|
+
|
|
112
|
+
1 + n_entity_cols + n_extra_cols + 1 # 1-based
|
|
113
|
+
|
|
114
|
+
for col in range(1, max_col + 1):
|
|
115
|
+
# --- Row 1: description row ---
|
|
116
|
+
cell_r1 = ws.cell(row=1, column=col)
|
|
117
|
+
if col == 1:
|
|
118
|
+
# 'navigate' cell gets link font, no dark fill
|
|
119
|
+
pass
|
|
120
|
+
elif cell_r1.value is not None:
|
|
121
|
+
cell_r1.fill = FILL_DESC_ROW
|
|
122
|
+
cell_r1.font = FONT_DESC_ROW
|
|
123
|
+
|
|
124
|
+
# --- Row 2: header row ---
|
|
125
|
+
cell_r2 = ws.cell(row=2, column=col)
|
|
126
|
+
if col == 1:
|
|
127
|
+
cell_r2.fill = FILL_ALT_HEADER
|
|
128
|
+
elif col <= 1 + n_entity_cols + n_extra_cols:
|
|
129
|
+
cell_r2.fill = FILL_ENTITY_HEADER
|
|
130
|
+
else:
|
|
131
|
+
cell_r2.fill = FILL_PARAM_HEADER
|
|
132
|
+
|
|
133
|
+
# --- Rows 3+: data rows ---
|
|
134
|
+
for row in range(3, max_row + 1):
|
|
135
|
+
cell = ws.cell(row=row, column=col)
|
|
136
|
+
if col == 1:
|
|
137
|
+
cell.fill = FILL_ALT_DATA
|
|
138
|
+
elif col <= 1 + n_entity_cols + n_extra_cols:
|
|
139
|
+
cell.fill = FILL_ENTITY_DATA
|
|
140
|
+
# parameter data cells: no fill (default white)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def format_periodic_sheet(
|
|
144
|
+
ws: Worksheet,
|
|
145
|
+
n_entity_cols: int,
|
|
146
|
+
n_extra_cols: int = 0,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Apply formatting to a periodic-layout sheet.
|
|
149
|
+
|
|
150
|
+
Same as constant but:
|
|
151
|
+
- The period / index column after entity columns gets entity colouring.
|
|
152
|
+
- No Entity Alternative column.
|
|
153
|
+
|
|
154
|
+
Column layout (1-based):
|
|
155
|
+
1 alternative
|
|
156
|
+
2 .. 1+n_entity_cols entity dimension columns
|
|
157
|
+
next n_extra_cols extra columns
|
|
158
|
+
remaining parameter columns
|
|
159
|
+
"""
|
|
160
|
+
max_row = ws.max_row
|
|
161
|
+
max_col = ws.max_column
|
|
162
|
+
|
|
163
|
+
entity_end_col = 1 + n_entity_cols + n_extra_cols # last entity-ish column
|
|
164
|
+
|
|
165
|
+
for col in range(1, max_col + 1):
|
|
166
|
+
# --- Row 1: description row ---
|
|
167
|
+
cell_r1 = ws.cell(row=1, column=col)
|
|
168
|
+
if col == 1:
|
|
169
|
+
pass # navigate link
|
|
170
|
+
elif cell_r1.value is not None:
|
|
171
|
+
cell_r1.fill = FILL_DESC_ROW
|
|
172
|
+
cell_r1.font = FONT_DESC_ROW
|
|
173
|
+
|
|
174
|
+
# --- Row 2: header row ---
|
|
175
|
+
cell_r2 = ws.cell(row=2, column=col)
|
|
176
|
+
if col == 1:
|
|
177
|
+
cell_r2.fill = FILL_ALT_HEADER
|
|
178
|
+
elif col <= entity_end_col:
|
|
179
|
+
cell_r2.fill = FILL_ENTITY_HEADER
|
|
180
|
+
else:
|
|
181
|
+
cell_r2.fill = FILL_PARAM_HEADER
|
|
182
|
+
|
|
183
|
+
# --- Rows 3+: data rows ---
|
|
184
|
+
for row in range(3, max_row + 1):
|
|
185
|
+
cell = ws.cell(row=row, column=col)
|
|
186
|
+
if col == 1:
|
|
187
|
+
cell.fill = FILL_ALT_DATA
|
|
188
|
+
elif col <= entity_end_col:
|
|
189
|
+
cell.fill = FILL_ENTITY_DATA
|
|
190
|
+
# parameter data cells: no fill
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def format_timeseries_sheet(
|
|
194
|
+
ws: Worksheet,
|
|
195
|
+
n_header_rows: int,
|
|
196
|
+
) -> None:
|
|
197
|
+
"""Apply formatting to a transposed timeseries-layout sheet.
|
|
198
|
+
|
|
199
|
+
Row layout (1-based):
|
|
200
|
+
1 alternative row (B='alternative', C+= alt values)
|
|
201
|
+
2 parameter row (B='parameter', C+= param names)
|
|
202
|
+
3 .. n_header_rows-1 entity dim rows (B=dim name, C+= entity names)
|
|
203
|
+
n_header_rows last entity/time (A='time', B=dim, C+= entity names)
|
|
204
|
+
n_header_rows+1 .. data rows (A=time values, C+= numeric data)
|
|
205
|
+
|
|
206
|
+
Column layout:
|
|
207
|
+
A navigate / empty / 'time' label / time values
|
|
208
|
+
B row labels ('alternative', 'parameter', entity dim names)
|
|
209
|
+
C+ data columns
|
|
210
|
+
"""
|
|
211
|
+
max_row = ws.max_row
|
|
212
|
+
max_col = ws.max_column
|
|
213
|
+
|
|
214
|
+
for row in range(1, max_row + 1):
|
|
215
|
+
for col in range(1, max_col + 1):
|
|
216
|
+
cell = ws.cell(row=row, column=col)
|
|
217
|
+
|
|
218
|
+
if col == 1:
|
|
219
|
+
# Column A
|
|
220
|
+
if row == 1:
|
|
221
|
+
# navigate cell – handled by add_navigate_link
|
|
222
|
+
pass
|
|
223
|
+
elif row < n_header_rows:
|
|
224
|
+
# empty cells above the time row
|
|
225
|
+
pass
|
|
226
|
+
elif row == n_header_rows:
|
|
227
|
+
cell.fill = FILL_TIME_HEADER
|
|
228
|
+
else:
|
|
229
|
+
# time data values
|
|
230
|
+
cell.fill = FILL_TIME_DATA
|
|
231
|
+
|
|
232
|
+
elif col == 2:
|
|
233
|
+
# Column B – row labels
|
|
234
|
+
if row == 1:
|
|
235
|
+
cell.fill = FILL_ALT_HEADER
|
|
236
|
+
elif row == 2:
|
|
237
|
+
cell.fill = FILL_PARAM_LABEL
|
|
238
|
+
elif row <= n_header_rows:
|
|
239
|
+
cell.fill = FILL_ENTITY_HEADER
|
|
240
|
+
# data rows in col B: no fill
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
# Columns C+ – data columns
|
|
244
|
+
if row == 1:
|
|
245
|
+
cell.fill = FILL_ALT_DATA
|
|
246
|
+
elif row == 2:
|
|
247
|
+
cell.fill = FILL_PARAM_HEADER
|
|
248
|
+
elif row <= n_header_rows:
|
|
249
|
+
cell.fill = FILL_ENTITY_DATA
|
|
250
|
+
# data rows: no fill
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def format_link_sheet(ws: Worksheet) -> None:
|
|
254
|
+
"""Apply formatting to a link-only (relationship) sheet.
|
|
255
|
+
|
|
256
|
+
Row 1: entity headers. Rows 2+: entity data.
|
|
257
|
+
"""
|
|
258
|
+
max_row = ws.max_row
|
|
259
|
+
max_col = ws.max_column
|
|
260
|
+
|
|
261
|
+
for col in range(1, max_col + 1):
|
|
262
|
+
ws.cell(row=1, column=col).fill = FILL_ENTITY_HEADER
|
|
263
|
+
for row in range(2, max_row + 1):
|
|
264
|
+
ws.cell(row=row, column=col).fill = FILL_ENTITY_DATA
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def add_navigate_link(ws: Worksheet) -> None:
|
|
268
|
+
"""Write a 'navigate' hyperlink in cell A1."""
|
|
269
|
+
cell = ws.cell(row=1, column=1)
|
|
270
|
+
cell.value = "navigate"
|
|
271
|
+
cell.hyperlink = "#navigate!A1"
|
|
272
|
+
cell.font = FONT_NAVIGATE_LINK
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ---------------------------------------------------------------------------
|
|
276
|
+
# v2 format formatting helpers
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def format_constant_sheet_v2(
|
|
281
|
+
ws: Worksheet,
|
|
282
|
+
n_entity_cols: int,
|
|
283
|
+
n_extra_cols: int = 0,
|
|
284
|
+
def_col: int = 3,
|
|
285
|
+
index_cols: set[int] | None = None,
|
|
286
|
+
last_data_col: int = 0,
|
|
287
|
+
def_row: int = 3,
|
|
288
|
+
) -> None:
|
|
289
|
+
"""Apply formatting to a v2 constant-layout sheet.
|
|
290
|
+
|
|
291
|
+
Row layout (1-based):
|
|
292
|
+
1 .. def_row-1 metadata rows (description, data type, optional
|
|
293
|
+
default — same dark/light fill pair regardless
|
|
294
|
+
of which slot they occupy)
|
|
295
|
+
def_row definition row (alternative, entity: X, parameter,
|
|
296
|
+
param names)
|
|
297
|
+
def_row+1 .. data
|
|
298
|
+
|
|
299
|
+
Column layout (1-based):
|
|
300
|
+
1 alternative
|
|
301
|
+
2 .. 1+n_entity_cols entity dimension columns
|
|
302
|
+
next n_extra_cols extra columns (direction, index, etc.)
|
|
303
|
+
def_col definition column (description/data type/parameter)
|
|
304
|
+
def_col+1 .. parameter columns
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
index_cols: 1-based column numbers that are index dimensions
|
|
308
|
+
(period, time, constraint) — these get a light green fill
|
|
309
|
+
instead of entity fill.
|
|
310
|
+
def_row: Row of the definition / parameter-name header. Each
|
|
311
|
+
metadata row above it (description, data type, optionally
|
|
312
|
+
default) shares the same fill pair.
|
|
313
|
+
"""
|
|
314
|
+
if index_cols is None:
|
|
315
|
+
index_cols = set()
|
|
316
|
+
|
|
317
|
+
max_row = ws.max_row
|
|
318
|
+
max_col = last_data_col if last_data_col > 0 else ws.max_column
|
|
319
|
+
|
|
320
|
+
for col in range(1, max_col + 1):
|
|
321
|
+
is_index = col in index_cols
|
|
322
|
+
|
|
323
|
+
# --- Metadata rows: 1..(def_row-1) ---
|
|
324
|
+
# Each one paints a label cell at def_col and data cells from
|
|
325
|
+
# def_col+1 onward. Row 1 column 1 is reserved for the navigate
|
|
326
|
+
# hyperlink and stays unfilled.
|
|
327
|
+
for meta_row in range(1, def_row):
|
|
328
|
+
cell = ws.cell(row=meta_row, column=col)
|
|
329
|
+
if col == 1 and meta_row == 1:
|
|
330
|
+
continue # navigate link
|
|
331
|
+
if col == def_col and cell.value is not None:
|
|
332
|
+
cell.fill = FILL_DESC_ROW
|
|
333
|
+
cell.font = FONT_DESC_ROW
|
|
334
|
+
elif col > def_col and cell.value is not None:
|
|
335
|
+
cell.fill = FILL_DESC_DATA
|
|
336
|
+
cell.font = FONT_DESC_DATA
|
|
337
|
+
|
|
338
|
+
# --- Definition row: def_row ---
|
|
339
|
+
cell_def = ws.cell(row=def_row, column=col)
|
|
340
|
+
if col == 1:
|
|
341
|
+
cell_def.fill = FILL_ALT_HEADER
|
|
342
|
+
elif is_index:
|
|
343
|
+
cell_def.fill = FILL_INDEX_HEADER
|
|
344
|
+
elif col < def_col:
|
|
345
|
+
cell_def.fill = FILL_ENTITY_HEADER
|
|
346
|
+
elif col == def_col:
|
|
347
|
+
cell_def.fill = FILL_PARAM_LABEL
|
|
348
|
+
else:
|
|
349
|
+
cell_def.fill = FILL_PARAM_HEADER
|
|
350
|
+
|
|
351
|
+
# --- Data rows: from def_row+1 onward ---
|
|
352
|
+
# Conditional formatting paints a 100-row buffer past the actual data.
|
|
353
|
+
data_start = def_row + 1
|
|
354
|
+
if max_row >= data_start:
|
|
355
|
+
from openpyxl.formatting.rule import CellIsRule
|
|
356
|
+
|
|
357
|
+
_always = ['"§§§NEVER§§§"']
|
|
358
|
+
data_range_end = max_row + 100
|
|
359
|
+
|
|
360
|
+
for col in range(1, (last_data_col if last_data_col > 0 else max_col) + 1):
|
|
361
|
+
is_index = col in index_cols
|
|
362
|
+
cl = get_column_letter(col)
|
|
363
|
+
rng = f"{cl}{data_start}:{cl}{data_range_end}"
|
|
364
|
+
|
|
365
|
+
if col == 1:
|
|
366
|
+
fill = FILL_ALT_DATA
|
|
367
|
+
elif is_index:
|
|
368
|
+
fill = FILL_INDEX_DATA
|
|
369
|
+
elif col == def_col:
|
|
370
|
+
fill = FILL_DEF_COL
|
|
371
|
+
elif col < def_col:
|
|
372
|
+
fill = FILL_ENTITY_DATA
|
|
373
|
+
elif col > def_col:
|
|
374
|
+
fill = FILL_PARAM_DATA
|
|
375
|
+
else:
|
|
376
|
+
continue
|
|
377
|
+
|
|
378
|
+
ws.conditional_formatting.add(
|
|
379
|
+
rng, CellIsRule(operator="notEqual", formula=_always, fill=fill)
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def format_periodic_sheet_v2(
|
|
384
|
+
ws: Worksheet,
|
|
385
|
+
n_entity_cols: int,
|
|
386
|
+
n_extra_cols: int = 0,
|
|
387
|
+
def_col: int = 3,
|
|
388
|
+
index_cols: set[int] | None = None,
|
|
389
|
+
last_data_col: int = 0,
|
|
390
|
+
def_row: int = 3,
|
|
391
|
+
) -> None:
|
|
392
|
+
"""Apply formatting to a v2 periodic-layout sheet.
|
|
393
|
+
|
|
394
|
+
Same structure as format_constant_sheet_v2 but for periodic sheets.
|
|
395
|
+
"""
|
|
396
|
+
format_constant_sheet_v2(
|
|
397
|
+
ws, n_entity_cols, n_extra_cols, def_col, index_cols,
|
|
398
|
+
last_data_col, def_row=def_row,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def format_timeseries_sheet_v2(
|
|
403
|
+
ws: Worksheet,
|
|
404
|
+
n_header_rows: int,
|
|
405
|
+
single_param: bool = False,
|
|
406
|
+
row_types: dict[int, str] | None = None,
|
|
407
|
+
last_data_col: int = 0,
|
|
408
|
+
) -> None:
|
|
409
|
+
"""Apply formatting to a v2 transposed timeseries-layout sheet.
|
|
410
|
+
|
|
411
|
+
Formatting is based on the *row_types* dict which maps 1-based row
|
|
412
|
+
numbers to their semantic type: 'description', 'data_type', 'alternative',
|
|
413
|
+
'parameter', 'entity', 'param_info' (grey single-param row).
|
|
414
|
+
|
|
415
|
+
If *row_types* is not provided, it is inferred from *single_param*.
|
|
416
|
+
"""
|
|
417
|
+
max_col = ws.max_column
|
|
418
|
+
|
|
419
|
+
if row_types is None:
|
|
420
|
+
row_types = {}
|
|
421
|
+
if single_param:
|
|
422
|
+
row_types = {1: "param_info", 2: "alternative", 3: "entity"}
|
|
423
|
+
else:
|
|
424
|
+
row_types = {1: "alternative", 2: "parameter", 3: "entity"}
|
|
425
|
+
|
|
426
|
+
# Map row type to (label_fill, data_fill) for col 2 and cols 3+
|
|
427
|
+
type_fills = {
|
|
428
|
+
"description": (FILL_DESC_ROW, FILL_DESC_DATA, FONT_DESC_ROW, FONT_DESC_DATA),
|
|
429
|
+
"data_type": (FILL_DESC_ROW, FILL_DESC_DATA, FONT_DESC_ROW, FONT_DESC_DATA),
|
|
430
|
+
"default": (FILL_DESC_ROW, FILL_DESC_DATA, FONT_DESC_ROW, FONT_DESC_DATA),
|
|
431
|
+
"alternative": (FILL_ALT_HEADER, FILL_ALT_DATA, None, None),
|
|
432
|
+
"parameter": (FILL_PARAM_LABEL, FILL_PARAM_HEADER, None, None),
|
|
433
|
+
"entity": (FILL_ENTITY_HEADER, FILL_ENTITY_DATA, None, None),
|
|
434
|
+
"param_info": (FILL_DEF_COL, FILL_DEF_COL, None, None),
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
# Format header rows (per-cell — only a few rows, only data columns)
|
|
438
|
+
header_col_end = last_data_col if last_data_col > 0 else max_col
|
|
439
|
+
for row in range(1, n_header_rows + 1):
|
|
440
|
+
rtype = row_types.get(row)
|
|
441
|
+
|
|
442
|
+
for col in range(1, header_col_end + 1):
|
|
443
|
+
cell = ws.cell(row=row, column=col)
|
|
444
|
+
|
|
445
|
+
if col == 1:
|
|
446
|
+
if row == n_header_rows:
|
|
447
|
+
cell.fill = FILL_INDEX_HEADER
|
|
448
|
+
# else: navigate link or empty — no fill
|
|
449
|
+
|
|
450
|
+
elif rtype in type_fills:
|
|
451
|
+
label_fill, data_fill, label_font, data_font = type_fills[rtype]
|
|
452
|
+
if col == 2:
|
|
453
|
+
cell.fill = label_fill
|
|
454
|
+
if label_font:
|
|
455
|
+
cell.font = label_font
|
|
456
|
+
else:
|
|
457
|
+
cell.fill = data_fill
|
|
458
|
+
if data_font:
|
|
459
|
+
cell.font = data_font
|
|
460
|
+
|
|
461
|
+
# Note: Column A data fill is NOT applied here — each writer adds its own
|
|
462
|
+
# (FILL_TIME_DATA for timeseries, FILL_INDEX_DATA for array sheets).
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def format_link_sheet_v2(ws: Worksheet) -> None:
|
|
466
|
+
"""Apply formatting to a v2 link-only (relationship) sheet.
|
|
467
|
+
|
|
468
|
+
Row 1: navigate
|
|
469
|
+
Row 2: entity definition row (entity: X, Y | dim1 | dim2)
|
|
470
|
+
Rows 3+: entity data
|
|
471
|
+
"""
|
|
472
|
+
max_row = ws.max_row
|
|
473
|
+
max_col = ws.max_column
|
|
474
|
+
|
|
475
|
+
for col in range(1, max_col + 1):
|
|
476
|
+
# Row 2: entity header row
|
|
477
|
+
ws.cell(row=2, column=col).fill = FILL_ENTITY_HEADER
|
|
478
|
+
# Rows 3+: entity data
|
|
479
|
+
for row in range(3, max_row + 1):
|
|
480
|
+
ws.cell(row=row, column=col).fill = FILL_ENTITY_DATA
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def auto_column_width(
|
|
484
|
+
ws: Worksheet,
|
|
485
|
+
min_param_width: float = 10,
|
|
486
|
+
non_param_width: float = 22,
|
|
487
|
+
def_col_width: float = 11,
|
|
488
|
+
index_col_width: float = 12,
|
|
489
|
+
max_width: float = 40,
|
|
490
|
+
header_row: int = 0,
|
|
491
|
+
def_col: int = 0,
|
|
492
|
+
index_cols: set[int] | None = None,
|
|
493
|
+
last_data_col: int = 0,
|
|
494
|
+
) -> None:
|
|
495
|
+
"""Size columns based on their role and header content.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
min_param_width: Minimum width for parameter columns (right of def_col).
|
|
499
|
+
non_param_width: Width for structural columns (alternative, entity).
|
|
500
|
+
def_col_width: Width for the definition column.
|
|
501
|
+
index_col_width: Width for index columns (period, time, constraint).
|
|
502
|
+
max_width: Maximum column width cap.
|
|
503
|
+
header_row: Row containing parameter names for sizing. When 0,
|
|
504
|
+
scan all rows (for navigate, scenario, etc.).
|
|
505
|
+
def_col: 1-based column of the definition column (the boundary).
|
|
506
|
+
When 0, all columns use content-based sizing.
|
|
507
|
+
index_cols: 1-based column numbers that are index dimensions.
|
|
508
|
+
last_data_col: Last column of the data area (1-based). Columns
|
|
509
|
+
beyond this are not sized (e.g. convenience/reference sections
|
|
510
|
+
set their own widths). When 0, size all columns.
|
|
511
|
+
"""
|
|
512
|
+
if index_cols is None:
|
|
513
|
+
index_cols = set()
|
|
514
|
+
|
|
515
|
+
end_col = last_data_col if last_data_col > 0 else (ws.max_column or 0)
|
|
516
|
+
for col_idx in range(1, end_col + 1):
|
|
517
|
+
if def_col > 0 and col_idx in index_cols:
|
|
518
|
+
width = index_col_width
|
|
519
|
+
elif def_col > 0 and col_idx < def_col:
|
|
520
|
+
width = non_param_width
|
|
521
|
+
elif def_col > 0 and col_idx == def_col:
|
|
522
|
+
width = def_col_width
|
|
523
|
+
elif def_col > 0 and header_row > 0:
|
|
524
|
+
# Parameter column — size to header row text
|
|
525
|
+
header_val = ws.cell(row=header_row, column=col_idx).value
|
|
526
|
+
if header_val is not None:
|
|
527
|
+
header_len = len(str(header_val)) + 1
|
|
528
|
+
else:
|
|
529
|
+
header_len = 0
|
|
530
|
+
width = max(min_param_width, min(header_len, max_width))
|
|
531
|
+
else:
|
|
532
|
+
# No def_col (navigate, scenario, etc.) — scan all rows
|
|
533
|
+
longest = non_param_width
|
|
534
|
+
for row_idx in range(1, min((ws.max_row or 0) + 1, 200)):
|
|
535
|
+
value = ws.cell(row=row_idx, column=col_idx).value
|
|
536
|
+
if value is not None:
|
|
537
|
+
length = len(str(value)) + 1
|
|
538
|
+
if length > longest:
|
|
539
|
+
longest = length
|
|
540
|
+
width = min(longest, max_width)
|
|
541
|
+
|
|
542
|
+
col_letter = get_column_letter(col_idx)
|
|
543
|
+
ws.column_dimensions[col_letter].width = width
|