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,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()
|