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,269 @@
1
+ """Open the project's results database(s) in the standalone Spine DB editor.
2
+
3
+ This is the Toolbox-interop launcher Tool. It resolves the per-project
4
+ ``results.sqlite`` at RUNTIME and launches the Spine DB editor on it,
5
+ DETACHED, so the Tool item completes immediately.
6
+
7
+ Two resolution modes (``--results-db-url`` overrides both):
8
+
9
+ * **From Output info (preferred):** ``--output-locations-db-url`` points at
10
+ the Output-info DB — the SAME source Re-create results and Scenario
11
+ comparison read. Each scenario's ``scenario/output_location`` records where
12
+ its outputs (and ``results.sqlite``) were written. We collect every DISTINCT
13
+ location and open every ``<location>/results.sqlite`` that exists — as tabs
14
+ in a single Spine DB editor (its ``main`` accepts multiple positional URLs).
15
+ Normally all scenarios share one project folder, so this is one DB.
16
+ * **From the project-folder redirect (fallback):** ``--project-folder-file``
17
+ resolves ``<project>/results.sqlite`` via ``resolve_output_path`` (the 5-tier
18
+ rule shared with the run Tool), for running this Tool without an Output-info
19
+ link.
20
+
21
+ Launch mechanism (the only supported standalone entry point — the
22
+ ``spine-db-editor`` console script, ``spinetoolbox.spine_db_editor.main:main``):
23
+ its ``main()`` parses positional ``url`` args, so we invoke
24
+
25
+ <python> -m spinetoolbox.spine_db_editor.main sqlite:///<path> [sqlite:///<path> ...]
26
+
27
+ with the SAME interpreter that runs this script (``sys.executable``).
28
+ """
29
+ import argparse
30
+ import subprocess
31
+ import sys
32
+ from pathlib import Path
33
+
34
+ from flextool.cli._console import run_tool
35
+ from flextool.cli.cmd_run_flextool import resolve_output_path
36
+
37
+
38
+ def resolve_results_db_path(project_folder_file, output_location=None):
39
+ """Resolve the filesystem path of the project's ``results.sqlite``.
40
+
41
+ Reuses ``resolve_output_path`` (the 5-tier rule shared with the run
42
+ Tool) to find the project/output root, then appends ``results.sqlite``.
43
+ """
44
+ output_root = resolve_output_path(
45
+ input_db_url=None,
46
+ flextool_location=None,
47
+ output_location=output_location,
48
+ cwd=Path.cwd(),
49
+ project_folder_file=project_folder_file,
50
+ )
51
+ return Path(output_root) / "results.sqlite"
52
+
53
+
54
+ def output_locations_from_db(output_info_db_url):
55
+ """Return the DISTINCT per-scenario ``output_location`` paths recorded in
56
+ the Output-info DB, in first-seen order.
57
+
58
+ Mirrors ``cmd_write_outputs``: scenarios come from the alternative filter
59
+ applied to the DB (or, unfiltered, from every ``scenario`` entity); each
60
+ scenario's ``scenario/output_location`` value names where its outputs were
61
+ written. The DB URL may be a ``sqlite:///`` path or a Spine DB-server
62
+ ``http://`` URL (both open via ``DatabaseMapping``).
63
+ """
64
+ from spinedb_api import DatabaseMapping
65
+
66
+ locations = []
67
+ with DatabaseMapping(output_info_db_url) as db:
68
+ filter_configs = db.get_filter_configs()
69
+ scenario_names = []
70
+ if filter_configs and filter_configs[0].get("alternatives"):
71
+ scenario_names = list(filter_configs[0]["alternatives"])
72
+ if not scenario_names:
73
+ scenario_names = [
74
+ e["name"]
75
+ for e in db.get_entity_items(entity_class_name="scenario")
76
+ ]
77
+ for scenario_name in scenario_names:
78
+ pv = db.get_parameter_value_item(
79
+ entity_class_name="scenario",
80
+ entity_byname=(scenario_name,),
81
+ parameter_definition_name="output_location",
82
+ alternative_name=scenario_name,
83
+ )
84
+ if not pv:
85
+ continue
86
+ loc = pv["parsed_value"]
87
+ if loc and loc not in locations:
88
+ locations.append(loc)
89
+ return locations
90
+
91
+
92
+ def _to_sqlite_url(db_path):
93
+ """Build a ``sqlite:///`` URL for an absolute/relative filesystem path."""
94
+ return f"sqlite:///{Path(db_path).as_posix()}"
95
+
96
+
97
+ def _build_launch_argv(db_urls):
98
+ """Build the argv that opens the standalone Spine DB editor.
99
+
100
+ ``db_urls`` may be a single URL string or a list of URLs (each becomes a
101
+ tab). The supported standalone entry point is
102
+ ``spinetoolbox.spine_db_editor.main:main``, whose ``main()`` reads
103
+ positional ``url`` args.
104
+ """
105
+ if isinstance(db_urls, str):
106
+ db_urls = [db_urls]
107
+ return [
108
+ sys.executable,
109
+ "-m",
110
+ "spinetoolbox.spine_db_editor.main",
111
+ *db_urls,
112
+ ]
113
+
114
+
115
+ def launch_db_editor(db_urls, _popen=subprocess.Popen):
116
+ """Launch the Spine DB editor on ``db_urls`` (str or list), DETACHED.
117
+
118
+ ``_popen`` is injectable for testing so the launch argv can be asserted
119
+ without spawning a real GUI process. Returns the spawned handle.
120
+ """
121
+ argv = _build_launch_argv(db_urls)
122
+ # start_new_session detaches the child into its own process group so it
123
+ # outlives this Tool process. No ``.wait()`` — the Tool returns at once.
124
+ # Redirect the child's std streams to DEVNULL so the detached editor does
125
+ # NOT inherit (and write to) Spine Toolbox's Basic Console pipes: otherwise
126
+ # the editor's import-time chatter — e.g. requests' RequestsDependencyWarning
127
+ # from the venv — leaks back into the Tool console long after this Tool has
128
+ # returned. A GUI app has its own window; it needs no console streams.
129
+ return _popen(
130
+ argv,
131
+ start_new_session=True,
132
+ stdin=subprocess.DEVNULL,
133
+ stdout=subprocess.DEVNULL,
134
+ stderr=subprocess.DEVNULL,
135
+ )
136
+
137
+
138
+ def _open_from_output_info(output_info_db_url):
139
+ """Open every existing ``<output_location>/results.sqlite`` found via the
140
+ Output-info DB, as tabs in one editor. Returns a process exit code."""
141
+ locations = output_locations_from_db(output_info_db_url)
142
+ if not locations:
143
+ print(
144
+ "No scenario output locations found in the Output-info DB "
145
+ f"({output_info_db_url}); run the workflow first.",
146
+ file=sys.stderr,
147
+ )
148
+ return 1
149
+ urls, missing = [], []
150
+ for loc in locations:
151
+ path = Path(loc) / "results.sqlite"
152
+ if path.exists():
153
+ urls.append(_to_sqlite_url(path))
154
+ else:
155
+ missing.append(path)
156
+ if missing:
157
+ print(
158
+ "results.sqlite missing at: "
159
+ + ", ".join(str(p) for p in missing)
160
+ + " (those scenarios were not run with output-spinedb).",
161
+ file=sys.stderr,
162
+ )
163
+ if not urls:
164
+ print(
165
+ "No results.sqlite found at any scenario output location; run "
166
+ "the workflow with output-spinedb enabled first.",
167
+ file=sys.stderr,
168
+ )
169
+ return 1
170
+ print(f"Opening Spine DB editor on {len(urls)} results database(s): "
171
+ + ", ".join(urls))
172
+ launch_db_editor(urls)
173
+ return 0
174
+
175
+
176
+ def main(argv=None):
177
+ parser = argparse.ArgumentParser(
178
+ description=(
179
+ "Open the project's results.sqlite in the standalone Spine DB "
180
+ "editor. Preferred: resolve locations from the Output-info DB "
181
+ "(--output-locations-db-url), the same source Re-create results "
182
+ "uses, opening every scenario's results.sqlite as tabs. "
183
+ "Fallback: resolve <project>/results.sqlite from "
184
+ "--project-folder-file."
185
+ )
186
+ )
187
+ parser.add_argument(
188
+ "--output-locations-db-url",
189
+ metavar="URL",
190
+ default=None,
191
+ help=(
192
+ "Output-info DB URL (sqlite:/// or http://). Opens every distinct "
193
+ "scenario <output_location>/results.sqlite that exists, as tabs."
194
+ ),
195
+ )
196
+ parser.add_argument(
197
+ "--project-folder-file",
198
+ metavar="PATH",
199
+ default=None,
200
+ help=(
201
+ "Fallback: path to the user-local file whose CONTENTS name the "
202
+ "project folder (templates/project_folder.txt). results.sqlite is "
203
+ "resolved at <project>/results.sqlite."
204
+ ),
205
+ )
206
+ parser.add_argument(
207
+ "--output-location",
208
+ metavar="PATH",
209
+ default=None,
210
+ help="Explicit project/output root override (used by the fallback).",
211
+ )
212
+ parser.add_argument(
213
+ "--results-db-url",
214
+ metavar="URL_OR_PATH",
215
+ default=None,
216
+ help=(
217
+ "Open this DB directly instead of resolving from Output info or "
218
+ "the project folder. Accepts a sqlite:/// URL or a bare path."
219
+ ),
220
+ )
221
+ args = parser.parse_args(argv)
222
+
223
+ # 1. Explicit --results-db-url short-circuits everything.
224
+ if args.results_db_url:
225
+ value = args.results_db_url
226
+ if "://" in value:
227
+ db_url = value
228
+ db_path = (
229
+ Path(value.replace("sqlite:///", "", 1))
230
+ if value.startswith("sqlite:")
231
+ else None
232
+ )
233
+ else:
234
+ db_path = Path(value)
235
+ db_url = _to_sqlite_url(db_path)
236
+ if db_path is not None and not db_path.exists():
237
+ print(
238
+ f"results.sqlite not found at {db_path}; run the workflow with "
239
+ f"output-spinedb enabled first (then re-run this Tool).",
240
+ file=sys.stderr,
241
+ )
242
+ return 1
243
+ print(f"Opening Spine DB editor on {db_url}")
244
+ launch_db_editor(db_url)
245
+ return 0
246
+
247
+ # 2. Preferred: resolve locations from the Output-info DB (open all).
248
+ if args.output_locations_db_url:
249
+ return _open_from_output_info(args.output_locations_db_url)
250
+
251
+ # 3. Fallback: the project-folder redirect → <project>/results.sqlite.
252
+ db_path = resolve_results_db_path(
253
+ args.project_folder_file, output_location=args.output_location
254
+ )
255
+ if not db_path.exists():
256
+ print(
257
+ f"results.sqlite not found at {db_path}; run the workflow with "
258
+ f"output-spinedb enabled first (then re-run this Tool).",
259
+ file=sys.stderr,
260
+ )
261
+ return 1
262
+ db_url = _to_sqlite_url(db_path)
263
+ print(f"Opening Spine DB editor on {db_url}")
264
+ launch_db_editor(db_url)
265
+ return 0
266
+
267
+
268
+ if __name__ == "__main__":
269
+ run_tool(main)
@@ -0,0 +1,66 @@
1
+ """CLI entry point for importing a MATPOWER .m file to a FlexTool Spine DB."""
2
+
3
+ import argparse
4
+
5
+ from flextool.process_inputs.read_matpower import (
6
+ create_flextool_db_from_matpower,
7
+ read_matpower,
8
+ )
9
+
10
+
11
+ def main() -> None:
12
+ parser = argparse.ArgumentParser(
13
+ description="Import a MATPOWER .m file into a FlexTool Spine DB "
14
+ "with DC power flow enabled.",
15
+ )
16
+ parser.add_argument(
17
+ "matpower_file",
18
+ help="Path to the MATPOWER .m file (e.g. pglib_opf_case14_ieee.m)",
19
+ )
20
+ parser.add_argument(
21
+ "target_db",
22
+ help="Path for the output SQLite database (e.g. case14.sqlite)",
23
+ )
24
+ parser.add_argument(
25
+ "--scenario-name",
26
+ default="dc_opf_test",
27
+ help="Name for the scenario (default: dc_opf_test)",
28
+ )
29
+ parser.add_argument(
30
+ "--alternative-name",
31
+ default="base",
32
+ help="Name for the alternative (default: base)",
33
+ )
34
+ parser.add_argument(
35
+ "--template-json",
36
+ default=None,
37
+ help="Path to FlexTool master template JSON "
38
+ "(default: schemas/spinedb_schema.json relative to cwd)",
39
+ )
40
+ args = parser.parse_args()
41
+
42
+ print(f"Reading MATPOWER file: {args.matpower_file}")
43
+ case = read_matpower(args.matpower_file)
44
+ print(
45
+ f" Parsed: {case.name} — {len(case.buses)} buses, "
46
+ f"{len(case.generators)} generators, {len(case.branches)} branches, "
47
+ f"baseMVA={case.base_mva}"
48
+ )
49
+
50
+ print(f"Writing FlexTool database: {args.target_db}")
51
+ url = create_flextool_db_from_matpower(
52
+ case,
53
+ args.target_db,
54
+ scenario_name=args.scenario_name,
55
+ alternative_name=args.alternative_name,
56
+ template_json=args.template_json,
57
+ )
58
+ print(f"Import complete! Database URL: {url}")
59
+ print(
60
+ f"Run with: python run_flextool.py {url} sqlite:///output.sqlite "
61
+ f"--scenario-name {args.scenario_name}"
62
+ )
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()
@@ -0,0 +1,63 @@
1
+ """CLI entry point for importing an old-format FlexTool .xlsm file to a Spine DB.
2
+
3
+ This runs the full pipeline: initialise the target from the frozen v56 import
4
+ template (if not already a FlexTool DB), write the workbook's data, then
5
+ ``migrate_database`` the result up to the current schema version. See
6
+ ``import_old_flextool_xlsm`` for why the importer is pinned to a fixed version
7
+ and relies on migration rather than tracking the live schema.
8
+ """
9
+
10
+ import argparse
11
+
12
+ from flextool.process_inputs.write_old_flextool_to_db import (
13
+ import_old_flextool_xlsm,
14
+ )
15
+
16
+
17
+ def main() -> None:
18
+ parser = argparse.ArgumentParser(
19
+ description="Import an old-format FlexTool Excel (.xlsm) into a Spine DB."
20
+ )
21
+ parser.add_argument(
22
+ "xlsm_path",
23
+ help="Path to the old-format FlexTool Excel file (.xlsm)",
24
+ )
25
+ parser.add_argument(
26
+ "target_db_url",
27
+ help="Target database URL (e.g. sqlite:///output.sqlite)",
28
+ )
29
+ parser.add_argument(
30
+ "--alternative-name",
31
+ default="base",
32
+ help="Name for the alternative (default: base)",
33
+ )
34
+ parser.add_argument(
35
+ "--no-purge",
36
+ action="store_true",
37
+ help="Do not purge existing data before importing",
38
+ )
39
+ parser.add_argument(
40
+ "--no-migrate",
41
+ action="store_true",
42
+ help=(
43
+ "Do not migrate the database to the current schema version after "
44
+ "writing. The GUI uses this so it can run migration as a separate "
45
+ "execution job; standalone use should leave migration on."
46
+ ),
47
+ )
48
+ args = parser.parse_args()
49
+
50
+ print(f"Importing old FlexTool Excel: {args.xlsm_path}")
51
+ print(f"Target database: {args.target_db_url}")
52
+ import_old_flextool_xlsm(
53
+ args.xlsm_path,
54
+ args.target_db_url,
55
+ alternative_name=args.alternative_name,
56
+ purge=not args.no_purge,
57
+ migrate=not args.no_migrate,
58
+ )
59
+ print("Import complete!")
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()
@@ -0,0 +1,50 @@
1
+ """CLI entry point for importing a self-describing FlexTool Excel file to a Spine DB."""
2
+
3
+ import argparse
4
+
5
+ from flextool.process_inputs.read_self_describing_excel import read_self_describing_excel
6
+ from flextool.process_inputs.write_self_describing_to_db import write_sheet_data_to_db
7
+
8
+
9
+ def main() -> None:
10
+ parser = argparse.ArgumentParser(
11
+ description="Import a self-describing FlexTool Excel (.xlsx) into a Spine DB."
12
+ )
13
+ parser.add_argument(
14
+ "xlsx_path",
15
+ help="Path to the self-describing Excel file (.xlsx)",
16
+ )
17
+ parser.add_argument(
18
+ "target_db_url",
19
+ help="Target database URL (e.g. sqlite:///output.sqlite)",
20
+ )
21
+ parser.add_argument(
22
+ "--no-purge",
23
+ action="store_true",
24
+ help="Do not purge existing data before importing",
25
+ )
26
+ parser.add_argument(
27
+ "--keep-entities",
28
+ action="store_true",
29
+ help="Keep existing entities during purge (for fresh template DBs)",
30
+ )
31
+ args = parser.parse_args()
32
+
33
+ print(f"Reading Excel: {args.xlsx_path}")
34
+ sheets = read_self_describing_excel(args.xlsx_path, skip_sheets={"navigate", "version"})
35
+
36
+ total_records = sum(len(s.records) for s in sheets)
37
+ total_links = sum(len(s.link_entities) for s in sheets)
38
+ print(f"Parsed {len(sheets)} sheets: {total_records} records, {total_links} links")
39
+
40
+ print(f"Writing to database: {args.target_db_url}")
41
+ write_sheet_data_to_db(
42
+ sheets, args.target_db_url,
43
+ purge_first=not args.no_purge,
44
+ keep_entities=args.keep_entities,
45
+ )
46
+ print("Import complete!")
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
@@ -0,0 +1,81 @@
1
+ import argparse
2
+ import os
3
+ from pathlib import Path
4
+ from flextool.process_inputs.read_tabular_with_specification import TabularReader
5
+ from flextool.process_inputs.write_to_input_db import write_to_flextool_input_db
6
+
7
+
8
+ def _ensure_target_db_exists(target_db_url: str) -> None:
9
+ """Initialize the target database from the FlexTool template if it does not exist.
10
+
11
+ The ``write_to_flextool_input_db`` function opens the database with
12
+ ``create=False`` and expects a valid FlexTool schema (including a
13
+ *version* parameter). When the caller asks us to write into a brand-new
14
+ sqlite file (e.g. during xlsx-to-sqlite conversion) the file won't exist
15
+ yet, so we must create and initialise it first.
16
+ """
17
+ if not target_db_url.startswith("sqlite:///"):
18
+ return # Only handle local sqlite files
19
+
20
+ db_path = target_db_url.replace("sqlite:///", "", 1)
21
+ if os.path.exists(db_path):
22
+ return # Already exists -- nothing to do
23
+
24
+ # Make sure the parent directory exists
25
+ parent = Path(db_path).parent
26
+ parent.mkdir(parents=True, exist_ok=True)
27
+
28
+ # Locate the FlexTool master template (bundled in the package).
29
+ from flextool._resources import package_data_path
30
+ json_template = package_data_path("schemas/spinedb_schema.json")
31
+ if not json_template.exists():
32
+ raise FileNotFoundError(
33
+ f"FlexTool template not found at {json_template}. "
34
+ "Cannot initialize the target database."
35
+ )
36
+
37
+ from flextool.update_flextool.initialize_database import initialize_database
38
+ from flextool.update_flextool.db_migration import migrate_database
39
+
40
+ print(f"Target database does not exist. Initializing: {db_path}")
41
+ initialize_database(str(json_template), db_path)
42
+ migrate_database(db_path)
43
+
44
+
45
+ def main():
46
+
47
+ parser = argparse.ArgumentParser()
48
+ parser.add_argument('target_db_url',help= "URL to FlexTool input database (e.g. sqlite:///input_data.sqlite)")
49
+
50
+ input_group = parser.add_mutually_exclusive_group(required=True)
51
+ input_group.add_argument('--tabular-file-path', help= "The file path of a FlexTool input file (either xlsx or ods).")
52
+ input_group.add_argument('--csv-directory-path', help= "Input data as csv files in FlexTool format.")
53
+ parser.add_argument('--migration-follows', action='store_true',
54
+ help="Accept a version mismatch because migration will run after import")
55
+
56
+ args = parser.parse_args()
57
+
58
+ # Initialize the target database if it doesn't exist yet
59
+ _ensure_target_db_exists(args.target_db_url)
60
+
61
+ # Get the path to the old 3.x format specification in process_inputs/
62
+ script_dir = os.path.dirname(os.path.abspath(__file__))
63
+ json_path = os.path.join(script_dir, "..", "process_inputs", "import_old_excel_input.json")
64
+ tabular_reader = TabularReader(json_path)
65
+
66
+ if args.tabular_file_path:
67
+ input_path = args.tabular_file_path
68
+ input_type = 'excel'
69
+ elif args.csv_directory_path:
70
+ input_path = args.csv_directory_path
71
+ input_type = 'csv'
72
+ else:
73
+ raise ValueError("Must provide either tabular_file_path or csv_directory_path")
74
+
75
+ write_to_flextool_input_db(input_path, tabular_reader, args.target_db_url, input_type,
76
+ migration_follows=args.migration_follows)
77
+
78
+
79
+
80
+ if __name__ == '__main__':
81
+ main()