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.
Files changed (322) hide show
  1. flextool/__init__.py +41 -0
  2. flextool/_mem_sampler.py +193 -0
  3. flextool/_resources.py +43 -0
  4. flextool/calibrate/__init__.py +51 -0
  5. flextool/calibrate/__main__.py +11 -0
  6. flextool/calibrate/_cli.py +316 -0
  7. flextool/calibrate/_db_alt.py +166 -0
  8. flextool/calibrate/_final_outputs.py +110 -0
  9. flextool/calibrate/_guard.py +151 -0
  10. flextool/calibrate/_loop.py +558 -0
  11. flextool/calibrate/_readers.py +223 -0
  12. flextool/calibrate/_report.py +263 -0
  13. flextool/calibrate/_sizing.py +699 -0
  14. flextool/calibrate/_solve.py +134 -0
  15. flextool/calibrate/_solve_status.py +495 -0
  16. flextool/cli/__init__.py +9 -0
  17. flextool/cli/_console.py +51 -0
  18. flextool/cli/_timing.py +147 -0
  19. flextool/cli/cmd_execute_flextool_workflow.py +187 -0
  20. flextool/cli/cmd_export_to_tabular.py +56 -0
  21. flextool/cli/cmd_import_sensitivities.py +75 -0
  22. flextool/cli/cmd_migrate_database.py +13 -0
  23. flextool/cli/cmd_open_results_db.py +269 -0
  24. flextool/cli/cmd_read_matpower.py +66 -0
  25. flextool/cli/cmd_read_old_flextool.py +63 -0
  26. flextool/cli/cmd_read_self_describing_tabular_input.py +50 -0
  27. flextool/cli/cmd_read_tabular_input.py +81 -0
  28. flextool/cli/cmd_run_flextool.py +1095 -0
  29. flextool/cli/cmd_scenario_results.py +284 -0
  30. flextool/cli/cmd_solve_mps.py +169 -0
  31. flextool/cli/cmd_update_flextool.py +17 -0
  32. flextool/cli/cmd_write_outputs.py +125 -0
  33. flextool/common_utils/__init__.py +1 -0
  34. flextool/common_utils/plot_mem_shape.py +77 -0
  35. flextool/common_utils/precision.py +451 -0
  36. flextool/decomposition/__init__.py +0 -0
  37. flextool/decomposition/region_decomposition.py +128 -0
  38. flextool/decomposition/region_filter.py +1261 -0
  39. flextool/engine_polars/__init__.py +110 -0
  40. flextool/engine_polars/_axis_enums.py +742 -0
  41. flextool/engine_polars/_benders.py +3462 -0
  42. flextool/engine_polars/_block_layout.py +1479 -0
  43. flextool/engine_polars/_blocks.py +1515 -0
  44. flextool/engine_polars/_commodity_ladder.py +660 -0
  45. flextool/engine_polars/_cumulative_invest.py +1165 -0
  46. flextool/engine_polars/_db_loader.py +153 -0
  47. flextool/engine_polars/_db_reader.py +127 -0
  48. flextool/engine_polars/_dc_power_flow.py +445 -0
  49. flextool/engine_polars/_delay.py +442 -0
  50. flextool/engine_polars/_derived_arithmetic.py +432 -0
  51. flextool/engine_polars/_derived_block.py +990 -0
  52. flextool/engine_polars/_derived_branch.py +769 -0
  53. flextool/engine_polars/_derived_existing.py +1353 -0
  54. flextool/engine_polars/_derived_npv.py +1297 -0
  55. flextool/engine_polars/_derived_params.py +9850 -0
  56. flextool/engine_polars/_derived_profile.py +881 -0
  57. flextool/engine_polars/_derived_walks.py +276 -0
  58. flextool/engine_polars/_determinism.py +70 -0
  59. flextool/engine_polars/_direct_params.py +2186 -0
  60. flextool/engine_polars/_dump_csvs.py +1009 -0
  61. flextool/engine_polars/_emit_arc_unions.py +1631 -0
  62. flextool/engine_polars/_emit_calc_params.py +729 -0
  63. flextool/engine_polars/_emit_chain_params.py +709 -0
  64. flextool/engine_polars/_emit_co2_accumulators.py +400 -0
  65. flextool/engine_polars/_emit_dispatchers.py +690 -0
  66. flextool/engine_polars/_emit_energy_margin.py +125 -0
  67. flextool/engine_polars/_emit_energy_margin_adder.py +290 -0
  68. flextool/engine_polars/_emit_entity_annual.py +428 -0
  69. flextool/engine_polars/_emit_inflow_scaling.py +1420 -0
  70. flextool/engine_polars/_emit_leaf_sets.py +550 -0
  71. flextool/engine_polars/_emit_lp_scaling.py +665 -0
  72. flextool/engine_polars/_emit_mid_sets.py +859 -0
  73. flextool/engine_polars/_emit_pdt_params.py +759 -0
  74. flextool/engine_polars/_emit_per_solve.py +774 -0
  75. flextool/engine_polars/_emit_period_calc.py +504 -0
  76. flextool/engine_polars/_emit_period_params.py +2398 -0
  77. flextool/engine_polars/_emit_provider_io.py +141 -0
  78. flextool/engine_polars/_emit_reserve.py +574 -0
  79. flextool/engine_polars/_emit_solve_time.py +311 -0
  80. flextool/engine_polars/_emit_solve_writers.py +1249 -0
  81. flextool/engine_polars/_flex_data_accumulator.py +388 -0
  82. flextool/engine_polars/_flex_data_provider.py +478 -0
  83. flextool/engine_polars/_group_slack.py +1253 -0
  84. flextool/engine_polars/_inmemory_reader.py +140 -0
  85. flextool/engine_polars/_input_source.py +336 -0
  86. flextool/engine_polars/_invest_seeds.py +191 -0
  87. flextool/engine_polars/_native_input_writer.py +100 -0
  88. flextool/engine_polars/_native_run_model.py +1348 -0
  89. flextool/engine_polars/_orchestration.py +4314 -0
  90. flextool/engine_polars/_output_writer.py +439 -0
  91. flextool/engine_polars/_param_shapes.py +1595 -0
  92. flextool/engine_polars/_parquet_bundle.py +723 -0
  93. flextool/engine_polars/_pdt_join.py +167 -0
  94. flextool/engine_polars/_pdt_lookup.py +547 -0
  95. flextool/engine_polars/_per_solve_sets.py +335 -0
  96. flextool/engine_polars/_projection_params.py +2056 -0
  97. flextool/engine_polars/_provider_keys.py +173 -0
  98. flextool/engine_polars/_provider_translators.py +225 -0
  99. flextool/engine_polars/_recursive_solve.py +703 -0
  100. flextool/engine_polars/_region_filter.py +2508 -0
  101. flextool/engine_polars/_reserve.py +649 -0
  102. flextool/engine_polars/_solve_acceptance.py +331 -0
  103. flextool/engine_polars/_solve_config.py +1001 -0
  104. flextool/engine_polars/_solve_context.py +885 -0
  105. flextool/engine_polars/_solve_handoff.py +164 -0
  106. flextool/engine_polars/_solve_state.py +232 -0
  107. flextool/engine_polars/_solver_base.py +36 -0
  108. flextool/engine_polars/_solver_dispatch.py +511 -0
  109. flextool/engine_polars/_spinedb_reader.py +1165 -0
  110. flextool/engine_polars/_stochastic.py +593 -0
  111. flextool/engine_polars/_subprocess_solve.py +1838 -0
  112. flextool/engine_polars/_timeline.py +1416 -0
  113. flextool/engine_polars/_vectorize.py +438 -0
  114. flextool/engine_polars/_warm.py +858 -0
  115. flextool/engine_polars/autoscale/__init__.py +107 -0
  116. flextool/engine_polars/autoscale/_config.py +218 -0
  117. flextool/engine_polars/autoscale/_layer2.py +1253 -0
  118. flextool/engine_polars/autoscale/_layer2_types.py +584 -0
  119. flextool/engine_polars/autoscale/_quantity_types.py +621 -0
  120. flextool/engine_polars/autoscale/_report.py +336 -0
  121. flextool/engine_polars/chain.py +259 -0
  122. flextool/engine_polars/input.py +6638 -0
  123. flextool/engine_polars/model.py +4754 -0
  124. flextool/env_check.py +388 -0
  125. flextool/export_to_tabular/__init__.py +5 -0
  126. flextool/export_to_tabular/db_reader.py +224 -0
  127. flextool/export_to_tabular/excel_writer.py +3559 -0
  128. flextool/export_to_tabular/export_settings.yaml +377 -0
  129. flextool/export_to_tabular/export_to_excel.py +227 -0
  130. flextool/export_to_tabular/formatting.py +543 -0
  131. flextool/export_to_tabular/sheet_config.py +876 -0
  132. flextool/gui/__init__.py +0 -0
  133. flextool/gui/__main__.py +118 -0
  134. flextool/gui/calibrate_commands.py +184 -0
  135. flextool/gui/calibrate_jobs.py +424 -0
  136. flextool/gui/check_tree.py +142 -0
  137. flextool/gui/cli_format.py +83 -0
  138. flextool/gui/config_parser.py +68 -0
  139. flextool/gui/data_models.py +362 -0
  140. flextool/gui/db_editor_integration.py +202 -0
  141. flextool/gui/db_version_check.py +269 -0
  142. flextool/gui/dialogs/__init__.py +0 -0
  143. flextool/gui/dialogs/add_dialog.py +1098 -0
  144. flextool/gui/dialogs/calibrate_dialog.py +1259 -0
  145. flextool/gui/dialogs/file_picker.py +473 -0
  146. flextool/gui/dialogs/group_picker.py +299 -0
  147. flextool/gui/dialogs/migration_consent_dialog.py +106 -0
  148. flextool/gui/dialogs/migration_progress_dialog.py +237 -0
  149. flextool/gui/dialogs/plot_dialog.py +459 -0
  150. flextool/gui/dialogs/plot_settings_picker.py +2184 -0
  151. flextool/gui/dialogs/project_dialog.py +426 -0
  152. flextool/gui/dialogs/update_dialog.py +212 -0
  153. flextool/gui/downsampling.py +88 -0
  154. flextool/gui/error_handling.py +50 -0
  155. flextool/gui/execution_manager.py +1715 -0
  156. flextool/gui/execution_window.py +1377 -0
  157. flextool/gui/hover_tooltip.py +111 -0
  158. flextool/gui/input_sources.py +730 -0
  159. flextool/gui/main_window.py +6181 -0
  160. flextool/gui/network_graph.py +215 -0
  161. flextool/gui/output_actions.py +393 -0
  162. flextool/gui/output_log_window.py +159 -0
  163. flextool/gui/platform_utils.py +421 -0
  164. flextool/gui/plot_cache.py +88 -0
  165. flextool/gui/plot_canvas.py +543 -0
  166. flextool/gui/plot_config_reader.py +272 -0
  167. flextool/gui/project_utils.py +100 -0
  168. flextool/gui/result_viewer.py +4394 -0
  169. flextool/gui/scenario_key.py +162 -0
  170. flextool/gui/scenario_lists.py +516 -0
  171. flextool/gui/settings_io.py +360 -0
  172. flextool/gui/solve_reader.py +103 -0
  173. flextool/gui/tree_reorder.py +88 -0
  174. flextool/gui/ui_metrics.py +420 -0
  175. flextool/input_derivation/__init__.py +281 -0
  176. flextool/input_derivation/_commodity_ladder.py +375 -0
  177. flextool/input_derivation/_commodity_ladder_sets.py +70 -0
  178. flextool/input_derivation/_dc_power_flow.py +377 -0
  179. flextool/input_derivation/_method_constants.py +77 -0
  180. flextool/input_derivation/_process_method.py +258 -0
  181. flextool/input_derivation/_specs.py +1026 -0
  182. flextool/input_derivation/_validators.py +321 -0
  183. flextool/lean_parquet.py +159 -0
  184. flextool/model_builder/__init__.py +5 -0
  185. flextool/model_builder/build_model.py +589 -0
  186. flextool/model_builder/encoding.py +67 -0
  187. flextool/model_builder/names.py +34 -0
  188. flextool/model_builder/profiles.py +129 -0
  189. flextool/plot_outputs/__init__.py +14 -0
  190. flextool/plot_outputs/axis_helpers.py +355 -0
  191. flextool/plot_outputs/color_template.py +888 -0
  192. flextool/plot_outputs/config.py +171 -0
  193. flextool/plot_outputs/format_helpers.py +345 -0
  194. flextool/plot_outputs/legend_helpers.py +143 -0
  195. flextool/plot_outputs/orchestrator.py +1141 -0
  196. flextool/plot_outputs/perf.py +37 -0
  197. flextool/plot_outputs/plan.py +1787 -0
  198. flextool/plot_outputs/plot_bars.py +1510 -0
  199. flextool/plot_outputs/plot_bars_detail.py +753 -0
  200. flextool/plot_outputs/plot_lines.py +951 -0
  201. flextool/plot_outputs/shared_manifest.py +564 -0
  202. flextool/plot_outputs/subplot_helpers.py +137 -0
  203. flextool/process_inputs/__init__.py +188 -0
  204. flextool/process_inputs/import_old_excel_input.json +4159 -0
  205. flextool/process_inputs/read_matpower.py +451 -0
  206. flextool/process_inputs/read_old_flextool.py +1288 -0
  207. flextool/process_inputs/read_self_describing_excel.py +1423 -0
  208. flextool/process_inputs/read_tabular_with_specification.py +1114 -0
  209. flextool/process_inputs/write_old_flextool_to_db.py +3077 -0
  210. flextool/process_inputs/write_self_describing_to_db.py +977 -0
  211. flextool/process_inputs/write_to_input_db.py +269 -0
  212. flextool/process_outputs/__init__.py +7 -0
  213. flextool/process_outputs/_annualize.py +55 -0
  214. flextool/process_outputs/_inmemory_helpers.py +292 -0
  215. flextool/process_outputs/_output_meta.py +672 -0
  216. flextool/process_outputs/calc_capacity_flows.py +107 -0
  217. flextool/process_outputs/calc_connections.py +136 -0
  218. flextool/process_outputs/calc_costs.py +260 -0
  219. flextool/process_outputs/calc_group_flows.py +192 -0
  220. flextool/process_outputs/calc_slacks.py +103 -0
  221. flextool/process_outputs/calc_storage_vre.py +160 -0
  222. flextool/process_outputs/drop_levels.py +208 -0
  223. flextool/process_outputs/handoff_writers.py +1315 -0
  224. flextool/process_outputs/out_ancillary.py +544 -0
  225. flextool/process_outputs/out_capacity.py +179 -0
  226. flextool/process_outputs/out_costs.py +334 -0
  227. flextool/process_outputs/out_flowgroup.py +189 -0
  228. flextool/process_outputs/out_flows.py +301 -0
  229. flextool/process_outputs/out_group.py +475 -0
  230. flextool/process_outputs/out_node.py +190 -0
  231. flextool/process_outputs/persist_realized_slice.py +601 -0
  232. flextool/process_outputs/process_results.py +24 -0
  233. flextool/process_outputs/read_highs_solution.py +2256 -0
  234. flextool/process_outputs/read_parameters.py +1799 -0
  235. flextool/process_outputs/read_sets.py +1095 -0
  236. flextool/process_outputs/read_variables.py +553 -0
  237. flextool/process_outputs/solve_order.py +81 -0
  238. flextool/process_outputs/spinedb_replay.py +412 -0
  239. flextool/process_outputs/union_realized_slice.py +224 -0
  240. flextool/process_outputs/write_outputs.py +1286 -0
  241. flextool/process_outputs/write_spinedb.py +1267 -0
  242. flextool/representative_periods/__init__.py +5 -0
  243. flextool/representative_periods/clustering.py +165 -0
  244. flextool/representative_periods/force_include.py +563 -0
  245. flextool/representative_periods/netload.py +365 -0
  246. flextool/representative_periods/netload_inputs.py +345 -0
  247. flextool/representative_periods/netload_iterate.py +722 -0
  248. flextool/representative_periods/preprocess.py +948 -0
  249. flextool/representative_periods/scenario_stack.py +195 -0
  250. flextool/representative_periods/weights.py +124 -0
  251. flextool/scenario_comparison/__init__.py +13 -0
  252. flextool/scenario_comparison/config_builder.py +158 -0
  253. flextool/scenario_comparison/constants.py +20 -0
  254. flextool/scenario_comparison/data_models.py +222 -0
  255. flextool/scenario_comparison/db_reader.py +399 -0
  256. flextool/scenario_comparison/dispatch_data.py +1002 -0
  257. flextool/scenario_comparison/dispatch_mappings.py +205 -0
  258. flextool/scenario_comparison/dispatch_plots.py +691 -0
  259. flextool/scenario_comparison/input_entity_colors.py +319 -0
  260. flextool/scenario_comparison/orchestrator.py +453 -0
  261. flextool/scenario_comparison/plan_union.py +244 -0
  262. flextool/scenario_comparison/plot_settings_seed.py +205 -0
  263. flextool/schemas/AXIS_CONTRACT.md +71 -0
  264. flextool/schemas/canonical_databases/howto_aggregate_output.json +6225 -0
  265. flextool/schemas/canonical_databases/howto_connections.json +5606 -0
  266. flextool/schemas/canonical_databases/howto_demand.json +5518 -0
  267. flextool/schemas/canonical_databases/howto_hydro_reservoir.json +6239 -0
  268. flextool/schemas/canonical_databases/howto_hydro_reservoir_with_pump.json +5933 -0
  269. flextool/schemas/canonical_databases/howto_non_sync_and_curtailment.json +5794 -0
  270. flextool/schemas/canonical_databases/howto_ramp_and_start_up.json +5707 -0
  271. flextool/schemas/canonical_databases/howto_stochastics.json +6032 -0
  272. flextool/schemas/canonical_databases/templates_examples.json +13532 -0
  273. flextool/schemas/canonical_databases/templates_time_settings_only.json +5340 -0
  274. flextool/schemas/comparison_settings_template.json +197 -0
  275. flextool/schemas/default_plot_settings.yaml +260 -0
  276. flextool/schemas/default_plots.yaml +2293 -0
  277. flextool/schemas/flextool_axis_contract.json +303 -0
  278. flextool/schemas/flextool_axis_contract.schema.json +247 -0
  279. flextool/schemas/old_flextool_import_template.json +4443 -0
  280. flextool/schemas/output_info_template.json +48 -0
  281. flextool/schemas/output_settings_template.json +256 -0
  282. flextool/schemas/pre_v26/flextool_template_constant_default.json +2105 -0
  283. flextool/schemas/pre_v26/flextool_template_default_optional_output.json +2152 -0
  284. flextool/schemas/pre_v26/flextool_template_default_value.json +2094 -0
  285. flextool/schemas/pre_v26/flextool_template_drop_down.json +2080 -0
  286. flextool/schemas/pre_v26/flextool_template_lifetime_method.json +1990 -0
  287. flextool/schemas/pre_v26/flextool_template_optional_outputs.json +2094 -0
  288. flextool/schemas/pre_v26/flextool_template_output_node_flows.json +2105 -0
  289. flextool/schemas/pre_v26/flextool_template_results_master.json +493 -0
  290. flextool/schemas/pre_v26/flextool_template_rolling_start_remove.json +2087 -0
  291. flextool/schemas/pre_v26/flextool_template_rolling_window.json +2059 -0
  292. flextool/schemas/pre_v26/flextool_template_storage_binding_defaults.json +46 -0
  293. flextool/schemas/pre_v26/flextool_template_v2.json +1990 -0
  294. flextool/schemas/pre_v26/flextool_template_v25.json +3864 -0
  295. flextool/schemas/spinedb_results_schema.json +581 -0
  296. flextool/schemas/spinedb_schema.json +4636 -0
  297. flextool/solver_config/copt.opt.template +18 -0
  298. flextool/solver_config/cplex.opt.template +25 -0
  299. flextool/solver_config/gurobi.opt.template +18 -0
  300. flextool/solver_config/highs.opt.template +18 -0
  301. flextool/solver_config/xpress.opt.template +26 -0
  302. flextool/spinedb_backend/__init__.py +26 -0
  303. flextool/spinedb_backend/_axis_enums.py +1119 -0
  304. flextool/spinedb_backend/_backend.py +1139 -0
  305. flextool/update_flextool/__init__.py +12 -0
  306. flextool/update_flextool/canonical_databases.py +251 -0
  307. flextool/update_flextool/db_migration.py +7108 -0
  308. flextool/update_flextool/ensure_settings_db.py +138 -0
  309. flextool/update_flextool/export_database.py +103 -0
  310. flextool/update_flextool/extend_tests_fixture.py +772 -0
  311. flextool/update_flextool/generate_canonical.py +274 -0
  312. flextool/update_flextool/initialize_database.py +42 -0
  313. flextool/update_flextool/install_info.py +225 -0
  314. flextool/update_flextool/self_update.py +464 -0
  315. flextool/update_flextool/sync_master_json_template.py +125 -0
  316. flextool/update_flextool/test_fixtures.py +187 -0
  317. flextool-4.0.0.dist-info/METADATA +217 -0
  318. flextool-4.0.0.dist-info/RECORD +322 -0
  319. flextool-4.0.0.dist-info/WHEEL +5 -0
  320. flextool-4.0.0.dist-info/entry_points.txt +17 -0
  321. flextool-4.0.0.dist-info/licenses/LICENSE.txt +19 -0
  322. flextool-4.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,377 @@
1
+ # Entity classes that need an Entity Alternative column (active_by_default=False).
2
+ entity_alternative_classes:
3
+ - node
4
+ - unit
5
+ - connection
6
+
7
+ # Parameters that must appear before the Entity Alternative column.
8
+ # Ensures roundtrip compatibility with the position-based importer.
9
+ pre_entity_alternative_params:
10
+ node: ["node_type"]
11
+
12
+ # Column width settings (unit ≈ width of character '0' in Calibri 11, ~7px).
13
+ minimum_parameter_column_width: 10
14
+ width_non_parameter_columns: 22
15
+ definition_column_width: 11
16
+ index_column_width: 12
17
+
18
+ # Default index column names per layout type (replaces 'x' from the DB).
19
+ index_name_defaults:
20
+ periodic: "period"
21
+ timeseries: "time"
22
+ nested_periodic: "period"
23
+
24
+ # Default index names for stochastic (nested Map) dimensions.
25
+ # Applied when the DB index_name is empty or 'x'.
26
+ stochastic_index_names:
27
+ - "forecast"
28
+ - "branch_time"
29
+ - "time"
30
+ - "is_realized"
31
+
32
+ # Entity classes that define time structure. Their Map parameters use timestep
33
+ # names as indexes but should NOT be filtered out by the time-indexed check.
34
+ time_structure_classes:
35
+ - timeline
36
+ - timeset
37
+
38
+ # Merge two entity classes onto one sheet set, adding a direction column.
39
+ merge_classes:
40
+ unit_node:
41
+ classes:
42
+ unit__inputNode: "input"
43
+ unit__outputNode: "output"
44
+ direction_column: "input_output"
45
+
46
+ # Unpack a Map parameter's indexes as an extra row-identifier column.
47
+ # Creates a separate sheet from the main entity class sheet.
48
+ unpack_map_params:
49
+ unit_node_constraint:
50
+ source_classes: ["unit__inputNode", "unit__outputNode"]
51
+ direction_column: "input_output"
52
+ params: ["constraint_flow_coeff"]
53
+ index_column: "constraint"
54
+ connection_node_constraint:
55
+ source_classes: ["connection__node"]
56
+ params: ["constraint_flow_coeff"]
57
+ index_column: "constraint"
58
+ node_constraint:
59
+ source_classes: ["node"]
60
+ params: ["constraint_invested_capacity_coeff", "constraint_cumulative_pre_built_capacity_coeff", "constraint_state_coeff"]
61
+ index_column: "constraint"
62
+ unit_constraint:
63
+ source_classes: ["unit"]
64
+ params: ["constraint_invested_capacity_coeff", "constraint_cumulative_pre_built_capacity_coeff"]
65
+ index_column: "constraint"
66
+ connection_constraint:
67
+ source_classes: ["connection"]
68
+ params: ["constraint_invested_capacity_coeff", "constraint_cumulative_pre_built_capacity_coeff"]
69
+ index_column: "constraint"
70
+
71
+ # Add columns from a related multi-dim entity class to a sheet.
72
+ add_entity_elements:
73
+ connection:
74
+ from_class: "connection__node__node"
75
+ columns: ["left_node", "right_node"]
76
+
77
+ # Split an entity class across multiple sheet groups with different param subsets.
78
+ split_params:
79
+ solve:
80
+ solve:
81
+ params: ["solve_mode", "solver",
82
+ "contains_solves", "solver_precommand",
83
+ "solver_arguments", "new_stepduration",
84
+ "solver_mip_gap", "scaling",
85
+ "timeline_hole_multiplier",
86
+ "decomposition", "benders_tolerance",
87
+ "benders_max_iter", "benders_in_out_weight"]
88
+ solve_period:
89
+ params: ["period_timeset", "years_represented", "realized_periods",
90
+ "invest_periods", "realized_invest_periods", "fix_storage_periods"]
91
+ rolling_parameters:
92
+ params: ["rolling_solve_jump", "rolling_solve_horizon", "rolling_duration"]
93
+ solve_s_stochastic_branches:
94
+ params: ["stochastic_branches"]
95
+ model:
96
+ model:
97
+ params: ["inflation_rate",
98
+ "inflation_offset_investment", "inflation_offset_operations",
99
+ "max_flow_for_unconstrained_variables",
100
+ "small_number_threshold",
101
+ "output_horizon"]
102
+ model_solve_sequence:
103
+ params: ["solves"]
104
+ model_periods_available:
105
+ params: ["periods_available"]
106
+ timeline:
107
+ timeline:
108
+ params: ["timestep_duration"]
109
+ timeset:
110
+ timeset_c:
111
+ params: ["timeline"]
112
+ timeset_t:
113
+ params: ["timeset_duration", "timeset_weights"]
114
+ timeset_s:
115
+ # representative_period_weights is a rank-2 nested Map (schema type
116
+ # 2d_map) — route it to the multi-index stochastic writer so the Map
117
+ # value survives round-trip (the periodic writer would drop it).
118
+ layout: stochastic
119
+ params: ["representative_period_weights"]
120
+
121
+ # Dimensions to drop from generated sheet names (always-present relationship dims).
122
+ drop_dimensions: ["upDown"]
123
+
124
+ # Rename generated base sheet names to match template conventions.
125
+ rename_sheets:
126
+ constraint: "constraint_sense"
127
+
128
+ # Sheet ordering
129
+ sheet_order:
130
+ - navigate
131
+ - scenario
132
+ - model
133
+ - model_solve_sequence
134
+ - model_periods_available
135
+ - solve
136
+ - solve_period
137
+ - solve_period_period
138
+ - rolling_parameters
139
+ - timeline
140
+ - timeset_c
141
+ - timeset_t
142
+ - timeset_s
143
+ - node_c
144
+ - node_p
145
+ - node_t
146
+ - node_s_inflow
147
+ - node_s_availability
148
+ - commodity_c
149
+ - commodity_p
150
+ - commodity_node
151
+ - connection_c
152
+ - connection_p
153
+ - connection_t
154
+ - unit_c
155
+ - unit_p
156
+ - unit_t
157
+ - unit_s_availability
158
+ - unit_s_efficiency
159
+ - unit_s_min_load
160
+ - unit_node_c
161
+ - unit_node_p
162
+ - unit_node_t
163
+ - unit_node_s
164
+ - profile_t
165
+ - profile_s
166
+ - node_profile_c
167
+ - connection_profile_c
168
+ - unit_node_profile_c
169
+ - group_c
170
+ - group_p
171
+ - group_connection
172
+ - group_connection_node
173
+ - group_node
174
+ - group_unit
175
+ - group_unit_node
176
+ - reserve_connection_node_c
177
+ - reserve_group_c
178
+ - reserve_group_p
179
+ - reserve_group_t
180
+ - reserve_group_s
181
+ - reserve_unit_node_c
182
+ - solve_s_stochastic_branches
183
+ - constraint_sense_c
184
+ - unit_node_constraint_c
185
+ - connection_node_constraint_c
186
+ - node_constraint_c
187
+ - unit_constraint_c
188
+ - connection_constraint_c
189
+ - connection_node
190
+
191
+ # Navigate sheet: groups of related sheets, each row lists sheet names
192
+ # placed across columns A (Constants), B (Periodic), C (Timeseries), D (Stochastic).
193
+ navigate_groups:
194
+ - name: "Configuration"
195
+ color: "D9D9D9"
196
+ rows:
197
+ - [scenario]
198
+ - [model]
199
+ - [model_solve_sequence]
200
+ - [model_periods_available]
201
+ - [solve, solve_period, solve_period_period, solve_s_stochastic_branches]
202
+ - [rolling_parameters]
203
+ - name: "Time"
204
+ color: "D9D9D9"
205
+ rows:
206
+ - [timeline]
207
+ - [timeset_c, timeset_t, "", timeset_s]
208
+ - name: "Core entities"
209
+ color: "D5C2EE"
210
+ rows:
211
+ - [node_c, node_p, node_t, node_s_inflow]
212
+ - ["", "", "", node_s_availability]
213
+ - [commodity_c, commodity_p]
214
+ - [commodity_node]
215
+ - [connection_c, connection_p, connection_t]
216
+ - [connection_node]
217
+ - [unit_c, unit_p, unit_t, unit_s_availability]
218
+ - ["", "", "", unit_s_efficiency]
219
+ - ["", "", "", unit_s_min_load]
220
+ - [unit_node_c, unit_node_p, unit_node_t, unit_node_s]
221
+ - name: "Profiles"
222
+ color: "C5E0B4"
223
+ rows:
224
+ - [profile_t, "", "", profile_s]
225
+ - [node_profile_c]
226
+ - [connection_profile_c]
227
+ - [unit_node_profile_c]
228
+ - name: "Groups"
229
+ color: "BDD7EE"
230
+ rows:
231
+ - [group_c, group_p]
232
+ - [group_connection]
233
+ - [group_connection_node]
234
+ - [group_node]
235
+ - [group_unit]
236
+ - [group_unit_node]
237
+ - name: "Reserves"
238
+ color: "F8CBAD"
239
+ rows:
240
+ - [reserve_connection_node_c, reserve_group_p, reserve_group_t, reserve_group_s]
241
+ - [reserve_group_c]
242
+ - [reserve_unit_node_c]
243
+ - name: "Constraints"
244
+ color: "FFE699"
245
+ rows:
246
+ - [constraint_sense_c]
247
+ - [unit_node_constraint_c]
248
+ - [connection_node_constraint_c]
249
+ - [node_constraint_c]
250
+ - [unit_constraint_c]
251
+ - [connection_constraint_c]
252
+
253
+ # Sheets that require the --include-advanced flag.
254
+ # Omitted by default; included when advanced features are needed or when the
255
+ # database contains data that requires them.
256
+ advanced_sheets:
257
+ - model_periods_available
258
+ - solve_period_period
259
+
260
+ # Parameter groups required for a functioning FlexTool model. The GUI
261
+ # group picker highlights these and ships them pre-checked. A user can
262
+ # still produce a valid input file without them by combining data from
263
+ # multiple input sources in Spine Toolbox. Order here is the display
264
+ # order in the picker (most foundational first).
265
+ required_groups:
266
+ - timeline
267
+ - model
268
+ - solve_basics
269
+ - basics
270
+
271
+ # When --groups is used, sheets listed here are kept even if all their
272
+ # parameters were filtered out, provided one of the listed parent groups
273
+ # is in the selection. Use this for structural sheets whose presence is
274
+ # needed to wire up a group's data even when the columns themselves live
275
+ # in another group.
276
+ #
277
+ # Example:
278
+ # solve_basics:
279
+ # - solve_period
280
+ always_include_with_groups: {}
281
+
282
+ # INFO rows: optional information text shown at the top of specific sheets.
283
+ # The reader ignores rows starting with "INFO:".
284
+ info_rows:
285
+ model: "INFO: There can be only one model per scenario - best to just use same model name always."
286
+ model_solve_sequence: "INFO: FlexTool can define one or more solves to be executed in sequence. They will be executed in top to down order from the column the model uses."
287
+
288
+ # Data type overrides for parameters where the DB type_list doesn't capture
289
+ # the full convention. Spine DB uses 2d-map with string "yes" values as a
290
+ # workaround for map-of-arrays (which it doesn't support natively).
291
+ data_type_overrides:
292
+ nested_periodic:
293
+ invest_periods: "string (2d-map)"
294
+ realized_invest_periods: "string (2d-map)"
295
+ realized_periods: "string (2d-map)"
296
+ fix_storage_periods: "string (2d-map)"
297
+
298
+ # Parameters that are ONLY period-indexed (never time-indexed).
299
+ # Used to filter them out of _t sheets. Derived from parameter descriptions.
300
+ # Parameters not listed here but having 1d_map in type_list are assumed to be
301
+ # usable on both _p and _t sheets.
302
+ period_only_params:
303
+ node:
304
+ - constraint_invested_capacity_coeff
305
+ - constraint_cumulative_pre_built_capacity_coeff
306
+ - constraint_state_coeff
307
+ - annual_flow
308
+ - cumulative_max_capacity
309
+ - cumulative_min_capacity
310
+ - existing
311
+ - fixed_cost
312
+ - interest_rate
313
+ - invest_cost
314
+ - invest_forced
315
+ - invest_max_period
316
+ - invest_min_period
317
+ - lifetime
318
+ - peak_inflow
319
+ - retire_max_period
320
+ - retire_min_period
321
+ - penalty_down
322
+ - penalty_up
323
+ - salvage_value
324
+ - self_discharge_loss
325
+ - storage_state_reference_price
326
+ - storage_state_reference_value
327
+ unit:
328
+ - constraint_invested_capacity_coeff
329
+ - constraint_cumulative_pre_built_capacity_coeff
330
+ - cumulative_max_capacity
331
+ - cumulative_min_capacity
332
+ - existing
333
+ - fixed_cost
334
+ - interest_rate
335
+ - invest_cost
336
+ - invest_max_period
337
+ - invest_min_period
338
+ - lifetime
339
+ - retire_max_period
340
+ - retire_min_period
341
+ - salvage_value
342
+ connection:
343
+ - constraint_invested_capacity_coeff
344
+ - constraint_cumulative_pre_built_capacity_coeff
345
+ - cumulative_max_capacity
346
+ - cumulative_min_capacity
347
+ - existing
348
+ - fixed_cost
349
+ - interest_rate
350
+ - invest_cost
351
+ - invest_max_period
352
+ - invest_min_period
353
+ - lifetime
354
+ - retire_max_period
355
+ - retire_min_period
356
+ - salvage_value
357
+ group:
358
+ - co2_max_period
359
+ - inertia_limit
360
+ - invest_max_period
361
+ - invest_min_period
362
+ - max_cumulative_flow
363
+ - max_instant_flow
364
+ - min_cumulative_flow
365
+ - min_instant_flow
366
+ - non_synchronous_limit
367
+ - penalty_capacity_margin
368
+ - penalty_inertia
369
+ - penalty_non_synchronous
370
+ solve:
371
+ - years_represented
372
+ unit__inputNode:
373
+ - constraint_flow_coeff
374
+ unit__outputNode:
375
+ - constraint_flow_coeff
376
+ connection__node:
377
+ - constraint_flow_coeff
@@ -0,0 +1,227 @@
1
+ """Top-level orchestrator for exporting a FlexTool Spine DB to an Excel file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from openpyxl import Workbook
9
+
10
+ from flextool.export_to_tabular.db_reader import DatabaseContents, read_database
11
+ from flextool.export_to_tabular.sheet_config import SheetSpec, build_sheet_specs, load_settings
12
+ from flextool.export_to_tabular.excel_writer import (
13
+ IndexClassifier,
14
+ write_array_transposed_sheet_v2,
15
+ write_constant_sheet,
16
+ write_constant_sheet_v2,
17
+ write_link_sheet,
18
+ write_link_sheet_v2,
19
+ write_navigate_sheet,
20
+ write_nested_periodic_sheet,
21
+ write_nested_periodic_sheet_v2,
22
+ write_periodic_sheet,
23
+ write_periodic_sheet_v2,
24
+ write_scenario_sheet,
25
+ write_ladder_sheet_v2,
26
+ write_stochastic_sheet_v2,
27
+ write_timeseries_sheet,
28
+ write_timeseries_sheet_v2,
29
+ write_version_sheet,
30
+ )
31
+ import flextool.export_to_tabular.excel_writer as _ew
32
+
33
+
34
+ def _build_tab_color_map(navigate_groups: list[dict[str, Any]]) -> dict[str, str]:
35
+ """Build a mapping from sheet name to tab color (hex RGB string) from navigate groups."""
36
+ color_map: dict[str, str] = {}
37
+ for group in navigate_groups:
38
+ color = group["color"]
39
+ for row in group.get("rows", []):
40
+ for sheet_name in row:
41
+ color_map[sheet_name] = color
42
+ return color_map
43
+
44
+
45
+ def export_to_excel(
46
+ db_url: str,
47
+ output_path: str,
48
+ include_advanced: bool = False,
49
+ use_new_format: bool = True,
50
+ include_stochastics: bool = False, # deprecated alias for include_advanced
51
+ include_groups: list[str] | None = None,
52
+ ) -> None:
53
+ """Export a FlexTool Spine DB to an Excel (.xlsx) file.
54
+
55
+ Args:
56
+ db_url: Spine DB URL (e.g., 'sqlite:///path/to/db.sqlite')
57
+ output_path: Output .xlsx file path
58
+ include_advanced: Whether to include advanced sheets (solve sequences,
59
+ periods_available, stochastic data).
60
+ use_new_format: If True, use the v2 self-describing format with embedded
61
+ metadata. If False, use the original v1 format.
62
+ include_stochastics: Deprecated alias for include_advanced.
63
+ include_groups: When given, restrict output to parameters whose
64
+ ``parameter_group_name`` is in the list (e.g. ``['basics',
65
+ 'model', 'timeline', 'solve_basics']``). Parameters with no
66
+ group are dropped. Sheets that lose all their columns are
67
+ dropped, unless listed under a selected group in
68
+ ``always_include_with_groups`` in ``export_settings.yaml``.
69
+ """
70
+ if include_stochastics:
71
+ include_advanced = True
72
+
73
+ # 1. Read all data from the database
74
+ print(f"Reading database: {db_url}")
75
+ db_contents: DatabaseContents = read_database(db_url)
76
+
77
+ # 1b. Build index classifier and entity alternatives index
78
+ _ew._index_classifier = IndexClassifier(db_contents)
79
+ _ew._build_entity_alts_index(db_contents)
80
+
81
+ # 2. Load settings and build ordered sheet specifications
82
+ print("Building sheet specifications...")
83
+ settings = load_settings()
84
+
85
+ # 2b. Load data type overrides and width settings
86
+ _ew._data_type_overrides = settings.get("data_type_overrides", {})
87
+ _ew._min_param_width = settings.get("minimum_parameter_column_width", 10)
88
+ _ew._non_param_width = settings.get("width_non_parameter_columns", 22)
89
+ _ew._def_col_width = settings.get("definition_column_width", 11)
90
+ _ew._index_col_width = settings.get("index_column_width", 12)
91
+ _ew._period_only_params = settings.get("period_only_params", {})
92
+ _ew._stochastic_index_names = settings.get("stochastic_index_names",
93
+ ["forecast", "branch_time", "time", "is_realized"])
94
+ _ew._time_structure_classes = set(settings.get("time_structure_classes", []))
95
+ specs: list[SheetSpec] = build_sheet_specs(
96
+ db_contents,
97
+ settings,
98
+ include_groups=set(include_groups) if include_groups else None,
99
+ )
100
+ navigate_groups: list[dict[str, Any]] = settings.get("navigate_groups", [])
101
+ tab_color_map = _build_tab_color_map(navigate_groups) if navigate_groups else {}
102
+
103
+ # Filter out advanced sheets unless requested OR they have data
104
+ advanced_sheets: set[str] = set(settings.get("advanced_sheets", []))
105
+ if not include_advanced:
106
+ # Pre-build a set of (class, param) pairs that have data for fast lookup
107
+ _data_keys: set[tuple[str, str]] = set()
108
+ for (cls, _bn, pn, _alt) in db_contents.parameter_values:
109
+ _data_keys.add((cls, pn))
110
+
111
+ def _has_data(spec: SheetSpec) -> bool:
112
+ for cls in spec.entity_classes:
113
+ for pn in spec.parameter_names:
114
+ if (cls, pn) in _data_keys:
115
+ return True
116
+ return False
117
+
118
+ specs = [s for s in specs
119
+ if s.sheet_name not in advanced_sheets or _has_data(s)]
120
+
121
+ # Load info rows setting
122
+ _ew._info_rows = settings.get("info_rows", {})
123
+
124
+ # 3. Create the workbook
125
+ wb = Workbook()
126
+
127
+ # 4. Delete the default 'Sheet' worksheet
128
+ if "Sheet" in wb.sheetnames:
129
+ del wb["Sheet"]
130
+
131
+ # Track counts by layout for the summary
132
+ layout_counts: dict[str, int] = {}
133
+
134
+ # 5. Write each sheet
135
+ for spec in specs:
136
+ ws = wb.create_sheet(spec.sheet_name)
137
+ layout = spec.layout
138
+
139
+ if layout == "constant":
140
+ if use_new_format:
141
+ # Check if this is a pure-array sheet (model_solve_sequence etc.)
142
+ _is_array_sheet = (
143
+ len(spec.parameter_names) == 1
144
+ and _ew._get_param_data_type(
145
+ spec.parameter_names[0], spec.entity_classes,
146
+ db_contents, layout="constant",
147
+ ) == "boolean (array)"
148
+ )
149
+ if _is_array_sheet:
150
+ write_array_transposed_sheet_v2(ws, spec, db_contents)
151
+ else:
152
+ write_constant_sheet_v2(ws, spec, db_contents)
153
+ else:
154
+ write_constant_sheet(ws, spec, db_contents)
155
+ elif layout == "periodic":
156
+ if use_new_format:
157
+ write_periodic_sheet_v2(ws, spec, db_contents)
158
+ else:
159
+ write_periodic_sheet(ws, spec, db_contents)
160
+ elif layout == "nested_periodic":
161
+ if use_new_format:
162
+ write_nested_periodic_sheet_v2(ws, spec, db_contents)
163
+ else:
164
+ write_nested_periodic_sheet(ws, spec, db_contents)
165
+ elif layout == "timeseries":
166
+ if use_new_format:
167
+ write_timeseries_sheet_v2(ws, spec, db_contents)
168
+ else:
169
+ write_timeseries_sheet(ws, spec, db_contents)
170
+ elif layout == "stochastic":
171
+ if use_new_format:
172
+ write_stochastic_sheet_v2(ws, spec, db_contents)
173
+ else:
174
+ # v1 has no stochastic writer — skip
175
+ del wb[spec.sheet_name]
176
+ continue
177
+ elif layout == "ladder":
178
+ if use_new_format:
179
+ write_ladder_sheet_v2(ws, spec, db_contents)
180
+ else:
181
+ # v1 has no ladder writer — skip
182
+ del wb[spec.sheet_name]
183
+ continue
184
+ elif layout == "link":
185
+ if use_new_format:
186
+ write_link_sheet_v2(ws, spec, db_contents)
187
+ else:
188
+ write_link_sheet(ws, spec, db_contents)
189
+ elif layout == "scenario":
190
+ write_scenario_sheet(ws, db_contents, include_stochastics=include_advanced)
191
+ elif layout == "version":
192
+ if use_new_format:
193
+ # In v2, version info is on the navigate sheet — skip separate sheet
194
+ del wb[spec.sheet_name]
195
+ continue
196
+ write_version_sheet(ws, db_contents.version)
197
+ elif layout == "navigate":
198
+ write_navigate_sheet(
199
+ ws, specs, navigate_groups=navigate_groups,
200
+ version=db_contents.version if use_new_format else None,
201
+ )
202
+ else:
203
+ print(f" Warning: unknown layout '{layout}' for sheet '{spec.sheet_name}', skipping.")
204
+ continue
205
+
206
+ # Set tab color from navigate group config
207
+ if spec.sheet_name in tab_color_map:
208
+ ws.sheet_properties.tabColor = tab_color_map[spec.sheet_name]
209
+
210
+ layout_counts[layout] = layout_counts.get(layout, 0) + 1
211
+
212
+ # 6. Ensure the output directory exists and save
213
+ output_dir = Path(output_path).parent
214
+ output_dir.mkdir(parents=True, exist_ok=True)
215
+
216
+ wb.save(output_path)
217
+
218
+ # 7. Print summary
219
+ format_label = "v2 (self-describing)" if use_new_format else "v1 (original)"
220
+ total = len(specs)
221
+ print(f"\nExport complete: {output_path} [{format_label}]")
222
+ print(f" Total sheets: {total}")
223
+ for layout_type, count in sorted(layout_counts.items()):
224
+ print(f" {layout_type}: {count}")
225
+ if db_contents.version is not None:
226
+ version_display = int(db_contents.version) if db_contents.version == int(db_contents.version) else db_contents.version
227
+ print(f" DB version: {version_display}")