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,173 @@
1
+ """Canonical Provider key constants.
2
+
3
+ Centralises the parent-qualified Provider keys
4
+ (``"solve_data/<basename>"``, ``"input/<basename>"``, etc.) used by
5
+ emit_* writers, cascade readers, and orchestration code.
6
+
7
+ Why this module exists
8
+ ----------------------
9
+
10
+ Phase 0a of ``specs/provider_consolidation.md`` retired the dual-key
11
+ registration in :func:`_emit_provider_io._emit`. Every frame lives
12
+ under exactly one Provider key — the parent-qualified form. Stringly-
13
+ typed lookups (``provider.get("solve_data/active_timelines")``) are
14
+ typo-prone: a rename in one site without the other becomes a silent
15
+ ``None`` return that the cascade tolerates by emitting an empty frame,
16
+ which then surfaces as wrong numbers downstream.
17
+
18
+ This module defines named Python constants for every key the cascade
19
+ emits or consumes. Producers do ``provider.put(K.SOLVE_DATA_ACTIVE_TIMELINES, df)``;
20
+ consumers do ``provider.get(K.SOLVE_DATA_ACTIVE_TIMELINES)``. IDE
21
+ rename / Find-Usages stays sound; a typo is a ``NameError`` at import
22
+ time, not a silent runtime miss.
23
+
24
+ Naming convention
25
+ -----------------
26
+
27
+ * The canonical form is **``"<parent>/<basename>"``** — no ``.csv``
28
+ suffix. Phase 3b retired the ``.csv`` suffix from key names: it
29
+ reinforced a misleading "this is a file" mental model, even though
30
+ the Provider stores frames in memory and strips ``.csv`` at lookup
31
+ time anyway. ``snapshot_processed_inputs`` adds the ``.csv``
32
+ suffix on disk dump.
33
+ * Constant names are uppercased, underscore-separated, prefixed with
34
+ the parent: ``SOLVE_DATA_ACTIVE_TIMELINES``, ``INPUT_TIMELINE``,
35
+ ``DERIVED_CT_METHOD_OVERRIDES``, ``HANDOFF_REALIZED_INVEST``.
36
+ * Group constants by parent dir, then alphabetically within the group.
37
+ * New keys: add the constant here first, then use it from the call sites.
38
+
39
+ Coverage scope
40
+ --------------
41
+
42
+ This module currently covers the **cross-solve carrier set**, the
43
+ **handoff carriers** (Phase 2 translator), and other high-traffic keys
44
+ touched by orchestration and the ``_solve_context`` loaders. The
45
+ single-use ``solve_data/*`` emit-and-forget keys (most of
46
+ :func:`_flex_data_accumulator.expected_basenames`) still appear as
47
+ inline string literals at their producer call sites — migrating them
48
+ to constants is a mechanical follow-up that can land in batches
49
+ without changing semantics.
50
+ """
51
+ from __future__ import annotations
52
+
53
+
54
+ SOLVE_DATA_FIX_STORAGE_QUANTITY = "solve_data/fix_storage_quantity"
55
+ SOLVE_DATA_FIX_STORAGE_PRICE = "solve_data/fix_storage_price"
56
+ SOLVE_DATA_FIX_STORAGE_USAGE = "solve_data/fix_storage_usage"
57
+ SOLVE_DATA_P_ENTITY_PRE_EXISTING = "solve_data/p_entity_pre_existing"
58
+ SOLVE_DATA_P_ENTITY_INVESTED = "solve_data/p_entity_invested"
59
+ SOLVE_DATA_P_ENTITY_DIVESTED = "solve_data/p_entity_divested"
60
+ SOLVE_DATA_P_ENTITY_PERIOD_EXISTING_CAPACITY = (
61
+ "solve_data/p_entity_period_existing_capacity"
62
+ )
63
+ SOLVE_DATA_P_ROLL_CONTINUE_STATE = "solve_data/p_roll_continue_state"
64
+ SOLVE_DATA_CO2_CUM_REALIZED_TONNES = "solve_data/co2_cum_realized_tonnes"
65
+ SOLVE_DATA_LADDER_CUM_SIM_HOURS = "solve_data/ladder_cum_sim_hours"
66
+ SOLVE_DATA_LADDER_CUM_REALIZED_MWH = "solve_data/ladder_cum_realized_mwh"
67
+ SOLVE_DATA_ED_HISTORY_REALIZED = "solve_data/ed_history_realized"
68
+ SOLVE_DATA_ED_HISTORY_REALIZED_FIRST = "solve_data/ed_history_realized_first"
69
+ SOLVE_DATA_EDD_HISTORY = "solve_data/edd_history"
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Frequently-referenced solve_data/ keys — touched by multiple cascade
74
+ # modules. Add to this section as call sites accumulate.
75
+ # ---------------------------------------------------------------------------
76
+
77
+ SOLVE_DATA_ACTIVE_TIMELINES = "solve_data/active_timelines"
78
+ SOLVE_DATA_INVEST_PERIODS_OF_CURRENT_SOLVE = (
79
+ "solve_data/invest_periods_of_current_solve"
80
+ )
81
+ SOLVE_DATA_PROCESS_SOURCE_TOSINK = "solve_data/process_source_toSink"
82
+ SOLVE_DATA_REGION_COUPLING = "solve_data/region_coupling"
83
+
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # RP-blended-weights keys — populated by ``_emit_solve_writers._compute_rp_frames``
87
+ # (the six derived sets/params) plus the node-state RP subset (``_emit_leaf_sets``)
88
+ # and the per-solve base-period set (``_emit_per_solve``). Consumed by the
89
+ # storage-binding-method machinery once the model-side wiring lands.
90
+ # ---------------------------------------------------------------------------
91
+
92
+ SOLVE_DATA_NODE_STATE_RP = "solve_data/nodeState_rp"
93
+ SOLVE_DATA_RP_BASE_CHAIN = "solve_data/rp_base_chain"
94
+ SOLVE_DATA_RP_BASE_FIRST = "solve_data/rp_base_first"
95
+ SOLVE_DATA_RP_BASE_LAST = "solve_data/rp_base_last"
96
+ SOLVE_DATA_RP_BASE_PERIOD_SET = "solve_data/rp_base_period_set"
97
+ SOLVE_DATA_RP_BLOCK_FIRST = "solve_data/rp_block_first"
98
+ SOLVE_DATA_RP_BLOCK_LAST = "solve_data/rp_block_last"
99
+ SOLVE_DATA_RP_BLOCK_START_LAST = "solve_data/rp_block_start_last"
100
+ SOLVE_DATA_RP_REP_PERIOD_SET = "solve_data/rp_rep_period_set"
101
+ SOLVE_DATA_RP_WEIGHTS = "solve_data/rp_weights"
102
+
103
+
104
+ # ---------------------------------------------------------------------------
105
+ # input/ keys — populated by ``seed_provider_from_dir(kind="input")`` and
106
+ # the input_derivation modules. Subset listed; expand as call sites grow.
107
+ # ---------------------------------------------------------------------------
108
+
109
+ INPUT_COMMODITY_LADDER_ANNUAL = "input/commodity_ladder_annual"
110
+ INPUT_COMMODITY_LADDER_CUMULATIVE = "input/commodity_ladder_cumulative"
111
+ INPUT_CONNECTION_DC_POWER_FLOW = "input/connection_dc_power_flow"
112
+ INPUT_GROUP__CO2_METHOD = "input/group__co2_method"
113
+ INPUT_NODE_DC_POWER_FLOW = "input/node_dc_power_flow"
114
+ INPUT_NODE_REFERENCE_ANGLE = "input/node_reference_angle"
115
+ INPUT_P_CONNECTION_SUSCEPTANCE = "input/p_connection_susceptance"
116
+ INPUT_PERIODS_AVAILABLE = "input/periods_available"
117
+ INPUT_PROCESS_METHOD = "input/process_method"
118
+
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # derived/ keys — populated by input_derivation between Spine read and
122
+ # cascade build. Distinct from input/ so derivation intermediates don't
123
+ # collide with raw user input.
124
+ # ---------------------------------------------------------------------------
125
+
126
+ DERIVED_CT_METHOD_OVERRIDES = "derived/ct_method_overrides"
127
+
128
+
129
+ # ---------------------------------------------------------------------------
130
+ # handoff/ keys — populated by ``_provider_translators.translate_handoff_to_provider``
131
+ # at iteration start, from the previous sub-solve's ``SolveHandoff``.
132
+ # One key per consumed handoff field; empty header-only frame written
133
+ # when the corresponding handoff field is ``None`` (first sub-solve, or
134
+ # carrier not active for the previous solve). Consumers should treat
135
+ # ``provider.get(K.HANDOFF_X).height == 0`` as the "no prior carrier"
136
+ # signal instead of checking the SolveHandoff object directly.
137
+ # ---------------------------------------------------------------------------
138
+
139
+ HANDOFF_REALIZED_INVEST = "handoff/realized_invest"
140
+ HANDOFF_REALIZED_EXISTING = "handoff/realized_existing"
141
+ HANDOFF_DIVEST_CUMULATIVE = "handoff/divest_cumulative"
142
+ HANDOFF_ROLL_END_STATE = "handoff/roll_end_state"
143
+ HANDOFF_UPWARD_ROLL_END_STATE = "handoff/upward_roll_end_state"
144
+ HANDOFF_CUMULATIVE_CO2 = "handoff/cumulative_co2"
145
+ HANDOFF_CUMULATIVE_COMMODITY = "handoff/cumulative_commodity"
146
+ HANDOFF_CUM_SIM_HOURS = "handoff/cum_sim_hours"
147
+ HANDOFF_FIX_STORAGE_QUANTITY = "handoff/fix_storage_quantity"
148
+ HANDOFF_FIX_STORAGE_PRICE = "handoff/fix_storage_price"
149
+ HANDOFF_FIX_STORAGE_USAGE = "handoff/fix_storage_usage"
150
+
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # override/ keys — parallel to handoff/* and populated by
154
+ # ``_provider_translators.translate_overrides_to_provider`` from an
155
+ # external overrides dict (Phase 5 of specs/provider_consolidation.md).
156
+ # When present, ``read_handoff_frame`` returns the override frame in
157
+ # preference to the natural handoff carrier, so existing consumers pick
158
+ # up overrides without code changes. Each override key shadows the
159
+ # correspondingly-named ``HANDOFF_*`` key; only the existing handoff
160
+ # carriers are overridable.
161
+ # ---------------------------------------------------------------------------
162
+
163
+ OVERRIDE_REALIZED_INVEST = "override/realized_invest"
164
+ OVERRIDE_REALIZED_EXISTING = "override/realized_existing"
165
+ OVERRIDE_DIVEST_CUMULATIVE = "override/divest_cumulative"
166
+ OVERRIDE_ROLL_END_STATE = "override/roll_end_state"
167
+ OVERRIDE_UPWARD_ROLL_END_STATE = "override/upward_roll_end_state"
168
+ OVERRIDE_CUMULATIVE_CO2 = "override/cumulative_co2"
169
+ OVERRIDE_CUMULATIVE_COMMODITY = "override/cumulative_commodity"
170
+ OVERRIDE_CUM_SIM_HOURS = "override/cum_sim_hours"
171
+ OVERRIDE_FIX_STORAGE_QUANTITY = "override/fix_storage_quantity"
172
+ OVERRIDE_FIX_STORAGE_PRICE = "override/fix_storage_price"
173
+ OVERRIDE_FIX_STORAGE_USAGE = "override/fix_storage_usage"
@@ -0,0 +1,225 @@
1
+ """Translators that fan typed orchestrator carriers into Provider entries.
2
+
3
+ Phase 2 of ``specs/provider_consolidation.md`` retires the
4
+ ``prior_handoff`` parameter threading through preprocessing. Instead of
5
+ passing the typed :class:`SolveHandoff` dataclass alongside the Provider
6
+ into every preprocessing call site, the orchestrator translates each
7
+ consumed handoff field into a dedicated Provider key
8
+ (``handoff/<field>.csv``) at iteration start. Preprocessing consumers
9
+ then read via ``provider.get(K.HANDOFF_X)`` — one read interface, no
10
+ parameter threading.
11
+
12
+ The translator writes an **empty header-only frame** when a handoff
13
+ field is ``None`` so consumers can use a single check
14
+ (``provider.get(K.X).height > 0``) to distinguish "prior carrier
15
+ populated" from "no prior carrier". This matches the
16
+ ``_read_csv``-style empty-frame contract used elsewhere in the cascade.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from pathlib import Path
21
+ from typing import TYPE_CHECKING
22
+
23
+ import polars as pl
24
+
25
+ from flextool.engine_polars import _provider_keys as K
26
+
27
+ if TYPE_CHECKING:
28
+ from flextool.engine_polars._flex_data_provider import FlexDataProvider
29
+ from flextool.engine_polars._solve_handoff import SolveHandoff
30
+
31
+
32
+ # Schemas for each handoff carrier's empty-frame fallback. Column names
33
+ # match what consumers iter_rows(named=True) keys; Utf8 dtype matches
34
+ # the canonical CSV-roundtrip shape (consumers cast to float on read).
35
+ _HANDOFF_EMPTY_SCHEMAS: dict[str, tuple[str, ...]] = {
36
+ K.HANDOFF_REALIZED_INVEST: ("entity", "period", "value"),
37
+ K.HANDOFF_REALIZED_EXISTING: ("entity", "period", "value"),
38
+ K.HANDOFF_DIVEST_CUMULATIVE: ("entity", "value"),
39
+ K.HANDOFF_ROLL_END_STATE: ("node", "value"),
40
+ K.HANDOFF_UPWARD_ROLL_END_STATE: ("node", "value"),
41
+ K.HANDOFF_CUMULATIVE_CO2: ("group", "period", "value"),
42
+ K.HANDOFF_CUMULATIVE_COMMODITY: ("commodity", "tier", "period",
43
+ "p_ladder_cum_realized_mwh"),
44
+ K.HANDOFF_CUM_SIM_HOURS: ("period", "p_ladder_cum_sim_hours"),
45
+ K.HANDOFF_FIX_STORAGE_QUANTITY: ("node", "period", "step", "p_fix_storage_quantity"),
46
+ K.HANDOFF_FIX_STORAGE_PRICE: ("node", "period", "step", "p_fix_storage_price"),
47
+ K.HANDOFF_FIX_STORAGE_USAGE: ("node", "period", "step", "p_fix_storage_usage"),
48
+ }
49
+
50
+
51
+ def _empty_handoff_frame(key: str) -> pl.DataFrame:
52
+ cols = _HANDOFF_EMPTY_SCHEMAS[key]
53
+ return pl.DataFrame(
54
+ {c: [] for c in cols},
55
+ schema={c: pl.Utf8 for c in cols},
56
+ )
57
+
58
+
59
+ def translate_handoff_to_provider(
60
+ handoff: "SolveHandoff | None", provider,
61
+ ) -> None:
62
+ """Write each consumed ``SolveHandoff`` field as a Provider entry.
63
+
64
+ Called at iteration start *before* preprocessing runs. When
65
+ *handoff* is ``None`` (first sub-solve, or no prior carrier
66
+ available) every key receives an empty header-only frame so
67
+ consumers can read unconditionally and check ``height > 0`` to
68
+ decide whether prior-carrier data is available.
69
+
70
+ The translated keys are listed in
71
+ ``flextool.engine_polars._provider_keys`` under the ``HANDOFF_*``
72
+ constants. See :data:`_HANDOFF_EMPTY_SCHEMAS` for column shapes.
73
+ """
74
+ field_to_key = (
75
+ ("realized_invest", K.HANDOFF_REALIZED_INVEST),
76
+ ("realized_existing", K.HANDOFF_REALIZED_EXISTING),
77
+ ("divest_cumulative", K.HANDOFF_DIVEST_CUMULATIVE),
78
+ ("roll_end_state", K.HANDOFF_ROLL_END_STATE),
79
+ ("upward_roll_end_state", K.HANDOFF_UPWARD_ROLL_END_STATE),
80
+ ("cumulative_co2", K.HANDOFF_CUMULATIVE_CO2),
81
+ ("cumulative_commodity", K.HANDOFF_CUMULATIVE_COMMODITY),
82
+ ("cum_sim_hours", K.HANDOFF_CUM_SIM_HOURS),
83
+ ("fix_storage_quantity", K.HANDOFF_FIX_STORAGE_QUANTITY),
84
+ ("fix_storage_price", K.HANDOFF_FIX_STORAGE_PRICE),
85
+ ("fix_storage_usage", K.HANDOFF_FIX_STORAGE_USAGE),
86
+ )
87
+ for field_name, key in field_to_key:
88
+ frame = getattr(handoff, field_name, None) if handoff is not None else None
89
+ if frame is None:
90
+ frame = _empty_handoff_frame(key)
91
+ provider.put(key, frame)
92
+
93
+
94
+ # Mapping from user-facing handoff Provider keys to their override
95
+ # siblings. Phase 5 of specs/provider_consolidation.md: external code
96
+ # supplies overrides keyed by ``K.HANDOFF_X`` (the user-facing identity);
97
+ # ``translate_overrides_to_provider`` writes each frame under the
98
+ # corresponding ``K.OVERRIDE_X`` key, and ``read_handoff_frame`` checks
99
+ # the override slot before falling back to the natural handoff carrier.
100
+ # Only the existing handoff carriers are overridable.
101
+ _HANDOFF_TO_OVERRIDE: dict[str, str] = {
102
+ K.HANDOFF_REALIZED_INVEST: K.OVERRIDE_REALIZED_INVEST,
103
+ K.HANDOFF_REALIZED_EXISTING: K.OVERRIDE_REALIZED_EXISTING,
104
+ K.HANDOFF_DIVEST_CUMULATIVE: K.OVERRIDE_DIVEST_CUMULATIVE,
105
+ K.HANDOFF_ROLL_END_STATE: K.OVERRIDE_ROLL_END_STATE,
106
+ K.HANDOFF_UPWARD_ROLL_END_STATE: K.OVERRIDE_UPWARD_ROLL_END_STATE,
107
+ K.HANDOFF_CUMULATIVE_CO2: K.OVERRIDE_CUMULATIVE_CO2,
108
+ K.HANDOFF_CUMULATIVE_COMMODITY: K.OVERRIDE_CUMULATIVE_COMMODITY,
109
+ K.HANDOFF_CUM_SIM_HOURS: K.OVERRIDE_CUM_SIM_HOURS,
110
+ K.HANDOFF_FIX_STORAGE_QUANTITY: K.OVERRIDE_FIX_STORAGE_QUANTITY,
111
+ K.HANDOFF_FIX_STORAGE_PRICE: K.OVERRIDE_FIX_STORAGE_PRICE,
112
+ K.HANDOFF_FIX_STORAGE_USAGE: K.OVERRIDE_FIX_STORAGE_USAGE,
113
+ }
114
+
115
+
116
+ def translate_overrides_to_provider(
117
+ overrides: "dict[str, pl.DataFrame] | None", provider,
118
+ ) -> None:
119
+ """Write external overrides under ``override/<field>`` Provider keys.
120
+
121
+ Phase 5 of ``specs/provider_consolidation.md``. External code provides
122
+ overrides keyed by ``K.HANDOFF_X`` constants (the user-facing identity);
123
+ this translator writes each frame under the corresponding ``K.OVERRIDE_X``
124
+ Provider key. Existing :func:`read_handoff_frame` consumers automatically
125
+ pick up overrides because the helper checks ``override/<field>`` before
126
+ falling back to ``handoff/<field>``.
127
+
128
+ When *overrides* is ``None`` or empty, no writes are made — the override
129
+ layer simply isn't present.
130
+
131
+ Raises
132
+ ------
133
+ ValueError
134
+ If *overrides* contains a key that is not one of the whitelisted
135
+ ``K.HANDOFF_*`` constants. Arbitrary Provider-key writes through
136
+ this translator are rejected so the override surface stays
137
+ explicit.
138
+ """
139
+ if not overrides:
140
+ return
141
+ for handoff_key, frame in overrides.items():
142
+ override_key = _HANDOFF_TO_OVERRIDE.get(handoff_key)
143
+ if override_key is None:
144
+ raise ValueError(
145
+ f"translate_overrides_to_provider: key {handoff_key!r} is not "
146
+ f"a whitelisted handoff carrier; only K.HANDOFF_* keys are "
147
+ f"overridable. Allowed keys: "
148
+ f"{sorted(_HANDOFF_TO_OVERRIDE.keys())!r}"
149
+ )
150
+ # Phase 6a — tag every override write so the audit dump can
151
+ # distinguish externally-supplied keys from natural-cascade
152
+ # writes. The override key itself is the discriminator at the
153
+ # dump site, so a single stable tag is sufficient.
154
+ provider.put(override_key, frame, source="external_override")
155
+
156
+
157
+ def read_handoff_frame(provider, key: str) -> "pl.DataFrame | None":
158
+ """Return a populated handoff frame from the Provider, or ``None``.
159
+
160
+ Phase 5: checks ``override/<field>`` first, falls back to
161
+ ``handoff/<field>``. External overrides written via
162
+ :func:`translate_overrides_to_provider` shadow the natural handoff
163
+ carrier; consumers stay key-stable.
164
+
165
+ The translator writes an empty header-only frame when the
166
+ corresponding handoff field is ``None``; this helper collapses the
167
+ ``height == 0`` empty back to ``None`` so consumers can preserve
168
+ their existing ``if frame is not None`` guard pattern. The same
169
+ ``height == 0 → None`` semantics apply to the override slot: an
170
+ empty override frame is treated as "no override present" and we
171
+ fall through to the handoff carrier.
172
+ """
173
+ if provider is None:
174
+ return None
175
+ override_key = _HANDOFF_TO_OVERRIDE.get(key)
176
+ if override_key is not None:
177
+ frame = provider.get(override_key)
178
+ if frame is not None and frame.height > 0:
179
+ return frame
180
+ frame = provider.get(key)
181
+ if frame is None or frame.height == 0:
182
+ return None
183
+ return frame
184
+
185
+
186
+ def dump_provider_sources(
187
+ provider: "FlexDataProvider",
188
+ dump_path: "str | Path",
189
+ solve_name: str,
190
+ ) -> None:
191
+ """Append every source-tagged Provider key to *dump_path*.
192
+
193
+ Phase 6b of ``specs/provider_consolidation.md``. Iterates the
194
+ Provider's keys; for each key whose :meth:`get_source` returns a
195
+ non-``None`` tag, appends one tab-separated line in the format::
196
+
197
+ <solve_name>\\t<key>\\t<source>\\n
198
+
199
+ The file is opened in append mode so multiple sub-solves
200
+ accumulate in a single log. The parent directory is created if
201
+ missing.
202
+
203
+ Untagged (natural-cascade) keys are skipped — the dump is a
204
+ minimal audit trail of writes whose origin differs from the
205
+ default preprocessing chain, not a snapshot of the full Provider.
206
+ """
207
+ path = Path(dump_path)
208
+ path.parent.mkdir(parents=True, exist_ok=True)
209
+ # Sorted iteration keeps the log deterministic across runs so the
210
+ # audit trail diffs cleanly between cascades that differ only in
211
+ # dict-iteration order.
212
+ with path.open("a", encoding="utf-8") as fh:
213
+ for key in sorted(provider.keys()):
214
+ source = provider.get_source(key)
215
+ if source is None:
216
+ continue
217
+ fh.write(f"{solve_name}\t{key}\t{source}\n")
218
+
219
+
220
+ __all__ = [
221
+ "translate_handoff_to_provider",
222
+ "translate_overrides_to_provider",
223
+ "read_handoff_frame",
224
+ "dump_provider_sources",
225
+ ]