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,303 @@
1
+ {
2
+ "_comment": "Canonical axis contract for FlexTool's pl.Enum dtype activation. Migrates hand-in-hand with schemas/spinedb_schema.json. See specs/enum_dtype_refactor_plan.md and schemas/AXIS_CONTRACT.md.",
3
+ "_doc": "schemas/AXIS_CONTRACT.md",
4
+ "_schema_version": "1.0.0",
5
+ "_template_master_version_compat": {
6
+ "path": "schemas/spinedb_schema.json",
7
+ "git_commit": "52df156c631443814246918051f68f8ae4d36571",
8
+ "git_commit_date": "2026-05-12 20:40:54 +0300",
9
+ "note": "Verified during Phase 0. Bump these fields when schemas/spinedb_schema.json changes in a way that affects axes (new/renamed/removed entity classes or parameter definitions referenced below)."
10
+ },
11
+ "axes": [
12
+ {
13
+ "name": "n",
14
+ "label": "node",
15
+ "source_type": "entity_class",
16
+ "source": "node",
17
+ "filter": "scenario_filter",
18
+ "column_synonyms": ["node"],
19
+ "note": "Node entities. Cascade column synonym: 'node' (renamed to 'n' in most helpers; see flextool/engine_polars/_axis_enums.py _AXIS_SYNONYMS)."
20
+ },
21
+ {
22
+ "name": "p",
23
+ "label": "process",
24
+ "source_type": "entity_class_union",
25
+ "source": ["unit", "connection"],
26
+ "filter": "scenario_filter",
27
+ "column_synonyms": ["process"],
28
+ "note": "FlexTool convention: 'process' = unit ∪ connection. Cascade column synonym: 'process'."
29
+ },
30
+ {
31
+ "name": "c",
32
+ "label": "commodity",
33
+ "source_type": "entity_class",
34
+ "source": "commodity",
35
+ "filter": "scenario_filter",
36
+ "column_synonyms": ["commodity"],
37
+ "note": "Commodity entities. WARNING: the cascade letter 'c' is overloaded — _direct_params._node_constraint_coef and _process_constraint_coef rename constraint→c (see review_notes.c_collision). Phase 1 build_axis_enums must decide whether to (a) rename the constraint column to 'cn' in those helpers or (b) split into two enums under different column names. The contract here documents the commodity vocabulary; the constraint axis is registered separately below."
38
+ },
39
+ {
40
+ "name": "g",
41
+ "label": "group",
42
+ "source_type": "entity_class",
43
+ "source": "group",
44
+ "filter": "scenario_filter",
45
+ "column_synonyms": ["group"]
46
+ },
47
+ {
48
+ "name": "f",
49
+ "label": "profile",
50
+ "source_type": "entity_class",
51
+ "source": "profile",
52
+ "filter": "scenario_filter"
53
+ },
54
+ {
55
+ "name": "d",
56
+ "label": "period",
57
+ "source_type": "parameter_keys",
58
+ "source": {
59
+ "entity_class": "solve",
60
+ "parameters": ["years_represented", "realized_periods", "invest_periods", "realized_invest_periods"],
61
+ "depth": 1,
62
+ "key_kind": "union_keys_and_array_values"
63
+ },
64
+ "filter": "∩ realized_periods of the active model instance",
65
+ "column_synonyms": ["period", "d_invest", "d_divest", "d_previous", "d_upper", "d_back"],
66
+ "note": "FlexTool 'period' axis. Sources: keys of solve.years_represented (Map), values of solve.realized_periods (Array), keys of solve.invest_periods (Map), keys of solve.realized_invest_periods (Map). NOT 'timeline' — the earlier audit reported source=timeline but the cascade renames period→d at every reader (e.g. _derived_block.py:169/180, _derived_branch.py:150, _invest_seeds.py:80/107). The 'period' is a logical year/horizon label, distinct from the timeline (timestep grid)."
67
+ },
68
+ {
69
+ "name": "e",
70
+ "label": "investable_entity",
71
+ "source_type": "entity_class_union",
72
+ "source": ["node", "unit", "connection"],
73
+ "filter": "scenario_filter",
74
+ "column_synonyms": ["entity"],
75
+ "note": "Union of node + process. Used in investment params and any frame mixing node and process names (source/sink). The substrate _AXIS_SYNONYMS already maps the column 'entity' → 'e', so 'e' IS the canonical entity-union axis. The separate 'entity' axis row in earlier drafts was a duplicate; consolidated here."
76
+ },
77
+ {
78
+ "name": "t",
79
+ "label": "timestep",
80
+ "source_type": "parameter_keys",
81
+ "source": {
82
+ "entity_class": "timeline",
83
+ "parameter": "timestep_duration",
84
+ "depth": 1
85
+ },
86
+ "filter": "∩ timeset.period_timestep values; union across active timelines",
87
+ "column_synonyms": [
88
+ "step",
89
+ "t_previous", "t_previous_within_block", "t_previous_within_timeset", "t_previous_within_solve",
90
+ "t_upper", "t_back", "t_source", "t_sink",
91
+ "b_first", "b_next"
92
+ ],
93
+ "note": "Vocabulary = keys of timeline.timestep_duration map. Per-timeline; union across active timelines if multiple are active in the model instance. 'b_first' and 'b_next' are confusingly named — they are cardinal timestep synonyms (the FIRST timestep of a coarse block) NOT 'block' axis values; see _derived_block.py:601-625."
94
+ },
95
+ {
96
+ "name": "i",
97
+ "label": "tier_index",
98
+ "source_type": "parameter_keys",
99
+ "source": {
100
+ "entity_class": "commodity",
101
+ "parameter_prefix": "price_ladder_",
102
+ "depth": "discover"
103
+ },
104
+ "filter": null,
105
+ "note": "Integer tier indices serialised as strings ('1', '2', …, 'N'). N discovered from max map depth across commodity.price_ladder_* parameters. Phase 1 build_axis_enums must inspect the runtime depth and emit a fixed-N enum."
106
+ },
107
+ {
108
+ "name": "branch",
109
+ "label": "stochastic_branch",
110
+ "source_type": "parameter_value_list",
111
+ "source": {
112
+ "entity_class": "solve",
113
+ "parameter": "stochastic_branches"
114
+ },
115
+ "filter": "gated by solve.include_stochastics",
116
+ "column_synonyms": ["b"],
117
+ "note": "Stochastic branch labels. The cascade column is 'b' (renamed from 'branch' in _derived_branch.py:150). WARNING: 'b' is overloaded — _derived_block.py / _block_layout.py also use 'b' for the BLOCK axis (see 'block' axis below). The two never appear in the same frame, but Phase 1 must decide on a disambiguation (e.g. rename block→bk in block helpers, or rename branch→br). See review_notes.b_collision. Empty enum is valid (deterministic runs)."
118
+ },
119
+ {
120
+ "name": "block",
121
+ "label": "block",
122
+ "source_type": "parameter_keys",
123
+ "source": {
124
+ "entity_class": "group",
125
+ "parameter": "new_stepduration",
126
+ "depth": "discover",
127
+ "key_kind": "values_plus_default"
128
+ },
129
+ "filter": "active blocks for the solve (includes synthetic DEFAULT_BLOCK)",
130
+ "column_synonyms": ["bk"],
131
+ "tokens_default_extension": ["default"],
132
+ "note": "Block names are derived in _block_layout.py from 'group' entities that carry a new_stepduration parameter, plus a synthetic DEFAULT_BLOCK fallback. Vocabulary = group names participating as resolution blocks ∪ {DEFAULT_BLOCK}. Phase 1 resolved the b_collision review note by renaming the cascade column from 'b' to 'bk' in _derived_block.py / _block_layout.py / input.py block-aware filters. The companion 'b_f' (block-fine) column also carries block vocabulary but is a distinct column name and does not collide."
133
+ },
134
+ {
135
+ "name": "r",
136
+ "label": "reserve",
137
+ "source_type": "entity_class",
138
+ "source": "reserve",
139
+ "filter": "scenario_filter",
140
+ "note": "Reserve entities. Cascade column: 'r' (renamed from 'reserve' in _reserve.py:160 and downstream)."
141
+ },
142
+ {
143
+ "name": "ud",
144
+ "label": "up_or_down",
145
+ "source_type": "synthetic",
146
+ "source": null,
147
+ "tokens": ["up", "down"],
148
+ "filter": null,
149
+ "note": "Fixed 2-value axis. Substrate of the schema is the 'upDown' entity class which has exactly two members ('up', 'down') — treated as synthetic to make the contract independent of the entity-class table for these two fixed tokens. Cascade column: 'ud' (renamed from 'upDown' in _reserve.py:160)."
150
+ },
151
+ {
152
+ "name": "constraint",
153
+ "label": "constraint",
154
+ "source_type": "entity_class",
155
+ "source": "constraint",
156
+ "filter": "scenario_filter",
157
+ "column_synonyms": ["cn"],
158
+ "note": "User-defined constraints. Phase 1 resolved the c-collision review note by renaming the constraint column from 'c' to 'cn' across _direct_params, _projection_params, input.py (_load_user_constraints), _derived_params (p_flow_constraint_coef_from_source) and model.py. The constraint axis now lives under the 'cn' column name and never collides with the commodity 'c' column."
159
+ },
160
+ {
161
+ "name": "d_anchor",
162
+ "label": "branch_anchor_period",
163
+ "source_type": "parameter_keys",
164
+ "source": {
165
+ "carrier": "period__branch",
166
+ "column": "d_anchor"
167
+ },
168
+ "filter": "subset of 'd' when stochastic",
169
+ "note": "Only populated when stochastic. Vocabulary = distinct anchor periods in the per-solve period__branch carrier. Derived; not a schema axis."
170
+ },
171
+ {
172
+ "name": "side",
173
+ "label": "flow_side",
174
+ "source_type": "synthetic",
175
+ "source": null,
176
+ "tokens": ["source", "sink"],
177
+ "filter": null,
178
+ "note": "Fixed pair. Substrate token introduced in cascade helpers (e.g. _derived_params.py and _writer_leaf_sets) when pivoting node/process membership by side."
179
+ },
180
+ {
181
+ "name": "klass",
182
+ "label": "process_klass",
183
+ "source_type": "synthetic",
184
+ "source": null,
185
+ "tokens": ["unit", "connection"],
186
+ "filter": null,
187
+ "note": "Fixed pair, mirrors the two entity classes that compose 'process'. Introduced in _derived_params.py efficiency-method derivation when tagging rows by their underlying process class."
188
+ }
189
+ ],
190
+ "synthetic_token_allowlist": [
191
+ {
192
+ "axis": "branch",
193
+ "tokens": ["eff", "noEff"],
194
+ "introduced_by": "_writer_co2_accumulators.py:160-164",
195
+ "note": "Literal suffix tags for efficiency-method branches in CO2 accumulator writer; must be present in the branch enum vocabulary even in deterministic runs."
196
+ },
197
+ {
198
+ "axis": "block",
199
+ "tokens": ["default"],
200
+ "introduced_by": "flextool/engine_polars/_block_layout.py DEFAULT_BLOCK",
201
+ "note": "Synthetic fallback block name assigned to nodes/processes that lack an explicit resolution group."
202
+ }
203
+ ],
204
+ "mixed_vocab_columns": {
205
+ "_comment": "Columns that hold a union vocabulary mixing entity classes. Cast against the 'e' (entity union) axis enum, not a column-name-matched single-class enum. The substrate _AXIS_SYNONYMS already maps 'entity' → 'e'; source/sink need an explicit cast against the same enum.",
206
+ "confirmed": ["source", "sink", "entity"],
207
+ "pending_audit": []
208
+ },
209
+ "non_dim_columns": {
210
+ "_comment": "String columns that look like dim columns by name but hold data values (method discriminators, scenario labels, etc). Leave as pl.Utf8 — do NOT cast.",
211
+ "confirmed": ["method", "scenario", "model", "solve", "value", "paramName", "class", "ts", "default_value"]
212
+ },
213
+ "parameter_value_dtypes": {
214
+ "_comment": "Inner-leaf dtype for Map/Array parameter VALUES. SpineDB does not enforce types inside Maps (e.g. a numeric coefficient may be authored as the string '1' instead of 1.0). This registry pins the expected leaf dtype per (entity_class, parameter) so SpineDBReader._unroll_rows casts the value column to a stable dtype instead of sniffing the first sample. Lenient cast (strict=False) parses 'looks-numeric' strings; values that fail to parse become null and are surfaced as FlexDataIntegrityError with an entity/index breadcrumb. Default = Float64; overrides list only parameters whose leaf is genuinely non-numeric.",
215
+ "default": "Float64",
216
+ "overrides": {
217
+ "solve.invest_periods": "Utf8",
218
+ "solve.realized_invest_periods": "Utf8",
219
+ "solve.realized_periods": "Utf8",
220
+ "solve.fix_storage_periods": "Utf8",
221
+ "solve.period_timeset": "Utf8",
222
+ "solve.stochastic_branches": "Utf8"
223
+ }
224
+ },
225
+ "_review_notes": [
226
+ {
227
+ "id": "c_collision",
228
+ "summary": "The cascade letter 'c' is used both for commodity (the commodity entity-class axis) and for constraint (in _direct_params._node_constraint_coef / _process_constraint_coef where the 'constraint' column is renamed to 'c').",
229
+ "code_sites": [
230
+ "flextool/engine_polars/_direct_params.py:285",
231
+ "flextool/engine_polars/_direct_params.py:306",
232
+ "flextool/engine_polars/input.py:1547",
233
+ "flextool/engine_polars/input.py:1564"
234
+ ],
235
+ "options": [
236
+ "(a) Rename the constraint-axis column to 'cn' in those helpers (lowest-risk; localised).",
237
+ "(b) Keep 'c' as a polymorphic column letter and rely on join siblings to disambiguate — requires Phase 2 cast_against_contract to inspect frame schema and pick the right enum per Param/frame.",
238
+ "(c) Promote 'cn' as the canonical constraint column and update the rename helpers."
239
+ ],
240
+ "recommendation_for_phase_1": "Pick option (a). The two helpers are the only sites; renaming there keeps build_axis_enums simple (one enum per column name).",
241
+ "resolution": "Phase 1 landed option (a): the constraint column is now 'cn' across the cascade (_direct_params, _projection_params.cdt_filter, input.py:_load_user_constraints, _derived_params.p_flow_constraint_coef_from_source, model.py user-constraint emission). The constraint axis 'column_synonyms' field reflects the new name."
242
+ },
243
+ {
244
+ "id": "b_collision",
245
+ "summary": "The cascade letter 'b' is used both for stochastic branch (in _derived_branch.py) and for block (in _derived_block.py / _block_layout.py). Both are derived axes with distinct vocabularies. No frame holds both at once, but Phase 1 must disambiguate to keep one enum-per-column-name in build_axis_enums.",
246
+ "code_sites": [
247
+ "flextool/engine_polars/_derived_branch.py:138, 150, 155, 177, 182, 191, 201",
248
+ "flextool/engine_polars/_derived_block.py:140, 165, 176",
249
+ "flextool/engine_polars/_block_layout.py:1435"
250
+ ],
251
+ "options": [
252
+ "(a) Rename block→'bk' and keep branch→'b'.",
253
+ "(b) Rename branch→'br' and keep block→'b'.",
254
+ "(c) Schema-aware cast helper that picks the right enum based on the surrounding columns ((d, b, w) → branch, (n, b) → block)."
255
+ ],
256
+ "recommendation_for_phase_1": "Pick option (a) (rename block→'bk'). branch is far more pervasive in stochastic-aware code; block is contained to ~4 modules and easier to rename.",
257
+ "resolution": "Phase 1 landed option (a). The block-axis column is now 'bk' in BlockBundle properties (entity_block_lf, block_period_time_first_lf, block_period_time_last_lf), in block_compat() and in the block-aware flow_to_n / flow_from_n filters in input.py and _derived_block.py. The branch column remains 'b'."
258
+ },
259
+ {
260
+ "id": "i_axis_depth",
261
+ "summary": "The 'i' (tier_index) axis depth is discovered at runtime from commodity.price_ladder_* parameter maps. Vocabulary is integer strings '1'..'N' but N is not in the schema.",
262
+ "code_sites": [
263
+ "flextool/engine_polars/_commodity_ladder.py"
264
+ ],
265
+ "options": [
266
+ "(a) Build the 'i' enum lazily at first use after backend ingest discovers N.",
267
+ "(b) Hard-cap N at some safe upper bound (e.g. 16) and pad — easier but wastes a little memory."
268
+ ],
269
+ "recommendation_for_phase_1": "Option (a). build_axis_enums runs after the Backend has read the ladder parameters so the max depth is known.",
270
+ "resolution": "Phase 1 landed option (a) — see flextool/spinedb_backend/_axis_enums.py::_discover_tier_vocabulary. build_axis_enums walks the level-2 keys of every commodity.price_ladder_* map at runtime and unions the tier labels (sorted numerically); no hard-coded N."
271
+ },
272
+ {
273
+ "id": "d_axis_source",
274
+ "summary": "The original audit reported axis 'd' source=timeline. Verification in the cascade (renames 'period'→'d' at many sites; no timeline-name lookup) shows 'd' is the PERIOD axis, not timeline. Sourced from solve.years_represented keys, solve.realized_periods array, solve.invest_periods keys, solve.realized_invest_periods keys.",
275
+ "code_sites": [
276
+ "flextool/engine_polars/_derived_block.py:169",
277
+ "flextool/engine_polars/_derived_block.py:180",
278
+ "flextool/engine_polars/_derived_branch.py:150",
279
+ "flextool/engine_polars/_invest_seeds.py:80",
280
+ "flextool/engine_polars/_invest_seeds.py:107"
281
+ ],
282
+ "resolution": "Updated axis row in this contract: source_type=parameter_keys with the four solve parameters listed."
283
+ },
284
+ {
285
+ "id": "entity_e_consolidation",
286
+ "summary": "The original audit listed 'entity' and 'e' as separate axes. The substrate at _axis_enums.py _AXIS_SYNONYMS already maps 'entity' → 'e' — they are the same axis (vocabulary = node ∪ unit ∪ connection) accessed via two column names.",
287
+ "resolution": "Consolidated into a single 'e' axis with column_synonyms=['entity']. source/sink also cast against this enum (see mixed_vocab_columns)."
288
+ },
289
+ {
290
+ "id": "ud_axis_synthetic_vs_entity_class",
291
+ "summary": "The 'upDown' entity class has exactly two members ('up', 'down'). Could be sourced from the entity class, but treated as synthetic in this contract because (1) the vocabulary is fixed by FlexTool semantics, (2) it never gains members, (3) keeping it synthetic decouples the Enum from the entity-class table.",
292
+ "options": [
293
+ "(a) source_type=synthetic with fixed tokens (current choice).",
294
+ "(b) source_type=entity_class, source=upDown — requires the Backend to enumerate the class."
295
+ ],
296
+ "recommendation_for_phase_1": "Phase 1 may switch to (b) for consistency if build_axis_enums prefers entity_class lookups over synthetic tokens. Either is fine; the tokens are identical."
297
+ },
298
+ {
299
+ "id": "block_source_classification",
300
+ "summary": "The 'block' axis is derived from 'group' entities that carry a new_stepduration parameter, plus a DEFAULT_BLOCK fallback. Strictly it could be classified as source_type=entity_class_subset (groups filtered by parameter presence) — modelled here as parameter_keys with key_kind=values_plus_default to keep the contract schema simple. Phase 1 build_axis_enums must traverse group entities and check for new_stepduration. Note: the internal field on BlockBundle is called block_step_duration but the SOURCE schema parameter is named new_stepduration."
301
+ }
302
+ ]
303
+ }
@@ -0,0 +1,247 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://irena-flextool.github.io/flextool/schemas/flextool_axis_contract.schema.json",
4
+ "title": "FlexTool axis contract",
5
+ "description": "Schema validating the structure of schemas/flextool_axis_contract.json. See schemas/AXIS_CONTRACT.md.",
6
+ "type": "object",
7
+ "required": ["axes"],
8
+ "properties": {
9
+ "_comment": { "type": "string" },
10
+ "_doc": { "type": "string" },
11
+ "_schema_version": { "type": "string" },
12
+ "_template_master_version_compat": {
13
+ "type": "object",
14
+ "required": ["path", "git_commit"],
15
+ "properties": {
16
+ "path": { "type": "string" },
17
+ "git_commit": {
18
+ "type": "string",
19
+ "pattern": "^[0-9a-f]{7,40}$"
20
+ },
21
+ "git_commit_date": { "type": "string" },
22
+ "note": { "type": "string" }
23
+ },
24
+ "additionalProperties": false
25
+ },
26
+ "axes": {
27
+ "type": "array",
28
+ "minItems": 1,
29
+ "items": { "$ref": "#/$defs/axis" }
30
+ },
31
+ "synthetic_token_allowlist": {
32
+ "type": "array",
33
+ "items": {
34
+ "type": "object",
35
+ "required": ["axis", "tokens"],
36
+ "properties": {
37
+ "axis": { "type": "string" },
38
+ "tokens": {
39
+ "type": "array",
40
+ "items": { "type": "string" },
41
+ "minItems": 1
42
+ },
43
+ "introduced_by": { "type": "string" },
44
+ "note": { "type": "string" }
45
+ },
46
+ "additionalProperties": false
47
+ }
48
+ },
49
+ "mixed_vocab_columns": {
50
+ "type": "object",
51
+ "required": ["confirmed", "pending_audit"],
52
+ "properties": {
53
+ "_comment": { "type": "string" },
54
+ "confirmed": {
55
+ "type": "array",
56
+ "items": { "type": "string" }
57
+ },
58
+ "pending_audit": {
59
+ "type": "array",
60
+ "items": { "type": "string" }
61
+ }
62
+ },
63
+ "additionalProperties": false
64
+ },
65
+ "non_dim_columns": {
66
+ "type": "object",
67
+ "required": ["confirmed"],
68
+ "properties": {
69
+ "_comment": { "type": "string" },
70
+ "confirmed": {
71
+ "type": "array",
72
+ "items": { "type": "string" }
73
+ }
74
+ },
75
+ "additionalProperties": false
76
+ },
77
+ "parameter_value_dtypes": {
78
+ "type": "object",
79
+ "required": ["default", "overrides"],
80
+ "properties": {
81
+ "_comment": { "type": "string" },
82
+ "default": {
83
+ "type": "string",
84
+ "enum": ["Float64", "Utf8", "Boolean"]
85
+ },
86
+ "overrides": {
87
+ "type": "object",
88
+ "patternProperties": {
89
+ "^[A-Za-z_][A-Za-z0-9_]*(__[A-Za-z_][A-Za-z0-9_]*)*\\.[A-Za-z_][A-Za-z0-9_]*$": {
90
+ "type": "string",
91
+ "enum": ["Float64", "Utf8", "Boolean"]
92
+ }
93
+ },
94
+ "additionalProperties": false
95
+ }
96
+ },
97
+ "additionalProperties": false
98
+ },
99
+ "_review_notes": {
100
+ "type": "array",
101
+ "items": {
102
+ "type": "object",
103
+ "required": ["id", "summary"],
104
+ "properties": {
105
+ "id": { "type": "string" },
106
+ "summary": { "type": "string" },
107
+ "code_sites": {
108
+ "type": "array",
109
+ "items": { "type": "string" }
110
+ },
111
+ "options": {
112
+ "type": "array",
113
+ "items": { "type": "string" }
114
+ },
115
+ "recommendation_for_phase_1": { "type": "string" },
116
+ "resolution": { "type": "string" }
117
+ },
118
+ "additionalProperties": false
119
+ }
120
+ }
121
+ },
122
+ "additionalProperties": false,
123
+ "$defs": {
124
+ "axis": {
125
+ "type": "object",
126
+ "required": ["name", "label", "source_type", "source"],
127
+ "properties": {
128
+ "name": {
129
+ "type": "string",
130
+ "minLength": 1
131
+ },
132
+ "label": { "type": "string" },
133
+ "source_type": {
134
+ "type": "string",
135
+ "enum": [
136
+ "entity_class",
137
+ "entity_class_union",
138
+ "parameter_keys",
139
+ "parameter_value_list",
140
+ "synthetic",
141
+ "union"
142
+ ]
143
+ },
144
+ "source": {},
145
+ "filter": { "type": ["string", "null"] },
146
+ "tokens": {
147
+ "type": "array",
148
+ "items": { "type": "string" }
149
+ },
150
+ "tokens_default_extension": {
151
+ "type": "array",
152
+ "items": { "type": "string" }
153
+ },
154
+ "column_synonyms": {
155
+ "type": "array",
156
+ "items": { "type": "string" }
157
+ },
158
+ "note": { "type": "string" }
159
+ },
160
+ "additionalProperties": false,
161
+ "allOf": [
162
+ {
163
+ "if": { "properties": { "source_type": { "const": "entity_class" } }, "required": ["source_type"] },
164
+ "then": { "properties": { "source": { "type": "string", "minLength": 1 } } }
165
+ },
166
+ {
167
+ "if": { "properties": { "source_type": { "const": "entity_class_union" } }, "required": ["source_type"] },
168
+ "then": {
169
+ "properties": {
170
+ "source": {
171
+ "type": "array",
172
+ "items": { "type": "string", "minLength": 1 },
173
+ "minItems": 2
174
+ }
175
+ }
176
+ }
177
+ },
178
+ {
179
+ "if": { "properties": { "source_type": { "const": "parameter_keys" } }, "required": ["source_type"] },
180
+ "then": {
181
+ "properties": {
182
+ "source": {
183
+ "type": "object",
184
+ "properties": {
185
+ "entity_class": { "type": "string" },
186
+ "parameter": { "type": "string" },
187
+ "parameters": {
188
+ "type": "array",
189
+ "items": { "type": "string" }
190
+ },
191
+ "parameter_prefix": { "type": "string" },
192
+ "depth": {},
193
+ "key_kind": { "type": "string" },
194
+ "carrier": { "type": "string" },
195
+ "column": { "type": "string" }
196
+ },
197
+ "additionalProperties": false
198
+ }
199
+ }
200
+ }
201
+ },
202
+ {
203
+ "if": { "properties": { "source_type": { "const": "parameter_value_list" } }, "required": ["source_type"] },
204
+ "then": {
205
+ "properties": {
206
+ "source": {
207
+ "type": "object",
208
+ "required": ["entity_class", "parameter"],
209
+ "properties": {
210
+ "entity_class": { "type": "string" },
211
+ "parameter": { "type": "string" }
212
+ },
213
+ "additionalProperties": false
214
+ }
215
+ }
216
+ }
217
+ },
218
+ {
219
+ "if": { "properties": { "source_type": { "const": "synthetic" } }, "required": ["source_type"] },
220
+ "then": {
221
+ "properties": {
222
+ "source": { "type": "null" },
223
+ "tokens": {
224
+ "type": "array",
225
+ "items": { "type": "string" },
226
+ "minItems": 1
227
+ }
228
+ },
229
+ "required": ["tokens"]
230
+ }
231
+ },
232
+ {
233
+ "if": { "properties": { "source_type": { "const": "union" } }, "required": ["source_type"] },
234
+ "then": {
235
+ "properties": {
236
+ "source": {
237
+ "type": "array",
238
+ "items": { "type": "string", "minLength": 1 },
239
+ "minItems": 2
240
+ }
241
+ }
242
+ }
243
+ }
244
+ ]
245
+ }
246
+ }
247
+ }