novomodelo-bridge 0.18.0__tar.gz

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 (455) hide show
  1. novomodelo_bridge-0.18.0/.claude/rules/bridge.md +97 -0
  2. novomodelo_bridge-0.18.0/.claude/rules/comments.md +194 -0
  3. novomodelo_bridge-0.18.0/.claude/rules/doc-integrity.md +88 -0
  4. novomodelo_bridge-0.18.0/.claude/rules/testing.md +70 -0
  5. novomodelo_bridge-0.18.0/.git-blame-ignore-revs +2 -0
  6. novomodelo_bridge-0.18.0/.github/workflows/ci.yml +61 -0
  7. novomodelo_bridge-0.18.0/.github/workflows/release.yml +55 -0
  8. novomodelo_bridge-0.18.0/.gitignore +39 -0
  9. novomodelo_bridge-0.18.0/CHANGELOG.md +1160 -0
  10. novomodelo_bridge-0.18.0/CLAUDE.md +82 -0
  11. novomodelo_bridge-0.18.0/CONTRIBUTING.md +150 -0
  12. novomodelo_bridge-0.18.0/LICENSE +203 -0
  13. novomodelo_bridge-0.18.0/NOTICE +26 -0
  14. novomodelo_bridge-0.18.0/PKG-INFO +199 -0
  15. novomodelo_bridge-0.18.0/README.md +174 -0
  16. novomodelo_bridge-0.18.0/docs/architecture.md +137 -0
  17. novomodelo_bridge-0.18.0/docs/cli.md +283 -0
  18. novomodelo_bridge-0.18.0/docs/decomp-data-map.md +2115 -0
  19. novomodelo_bridge-0.18.0/docs/decomp.md +98 -0
  20. novomodelo_bridge-0.18.0/docs/lineage/decomp.toml +3271 -0
  21. novomodelo_bridge-0.18.0/docs/lineage/newave.toml +3269 -0
  22. novomodelo_bridge-0.18.0/docs/newave-data-map.md +1823 -0
  23. novomodelo_bridge-0.18.0/docs/newave.md +119 -0
  24. novomodelo_bridge-0.18.0/docs/real-deck-checks.md +133 -0
  25. novomodelo_bridge-0.18.0/pyproject.toml +80 -0
  26. novomodelo_bridge-0.18.0/pyrightconfig.json +27 -0
  27. novomodelo_bridge-0.18.0/ruff.toml +18 -0
  28. novomodelo_bridge-0.18.0/scripts/README.md +38 -0
  29. novomodelo_bridge-0.18.0/scripts/analyze_results.py +469 -0
  30. novomodelo_bridge-0.18.0/scripts/ci/_scan.py +63 -0
  31. novomodelo_bridge-0.18.0/scripts/ci/check_comment_bloat.py +89 -0
  32. novomodelo_bridge-0.18.0/scripts/ci/check_comment_refs.py +106 -0
  33. novomodelo_bridge-0.18.0/scripts/ci/check_doc_paths.py +143 -0
  34. novomodelo_bridge-0.18.0/scripts/ci/check_no_plan_leaks.py +114 -0
  35. novomodelo_bridge-0.18.0/scripts/ci/quality_report.py +149 -0
  36. novomodelo_bridge-0.18.0/scripts/gen-cli-docs.sh +17 -0
  37. novomodelo_bridge-0.18.0/scripts/gen-lineage-docs.py +96 -0
  38. novomodelo_bridge-0.18.0/scripts/lineage/__init__.py +8 -0
  39. novomodelo_bridge-0.18.0/scripts/lineage/model.py +603 -0
  40. novomodelo_bridge-0.18.0/scripts/lineage/render.py +422 -0
  41. novomodelo_bridge-0.18.0/scripts/lineage/trace.py +274 -0
  42. novomodelo_bridge-0.18.0/scripts/pre-commit +30 -0
  43. novomodelo_bridge-0.18.0/scripts/regen-goldens.sh +16 -0
  44. novomodelo_bridge-0.18.0/src/novomodelo_bridge/__init__.py +3 -0
  45. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/__init__.py +7 -0
  46. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/__main__.py +4 -0
  47. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/app.py +511 -0
  48. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/args.py +116 -0
  49. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/check.py +73 -0
  50. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/compare.py +399 -0
  51. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/config.py +225 -0
  52. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/conversion_manifest.py +175 -0
  53. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/convert.py +604 -0
  54. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/dashboard.py +90 -0
  55. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/failure.py +60 -0
  56. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/logging_config.py +105 -0
  57. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/validate.py +193 -0
  58. novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/verdict.py +240 -0
  59. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/__init__.py +1 -0
  60. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/analyze.py +1827 -0
  61. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/__init__.py +106 -0
  62. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/_shared.py +356 -0
  63. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/constraints.py +190 -0
  64. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/convergence.py +99 -0
  65. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/costs.py +544 -0
  66. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/fpha.py +297 -0
  67. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/hydro.py +515 -0
  68. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/network.py +213 -0
  69. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/overview.py +84 -0
  70. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/performance.py +252 -0
  71. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/productivity.py +284 -0
  72. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/spillage.py +115 -0
  73. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/system.py +252 -0
  74. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/thermal.py +59 -0
  75. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/constraints.py +210 -0
  76. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/dataset.py +570 -0
  77. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/decomp/__init__.py +1 -0
  78. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/decomp/readers.py +410 -0
  79. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/decomp/results.py +3121 -0
  80. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/export.py +208 -0
  81. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/fpha.py +169 -0
  82. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/html_report.py +69 -0
  83. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/manifest.py +94 -0
  84. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/model.py +265 -0
  85. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/__init__.py +1 -0
  86. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/alignment.py +189 -0
  87. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/constraints.py +471 -0
  88. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/readers.py +1094 -0
  89. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/results.py +1311 -0
  90. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/report_builder.py +1234 -0
  91. novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/verdict.py +98 -0
  92. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/__init__.py +1 -0
  93. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/bound_merge.py +69 -0
  94. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/conversion.py +66 -0
  95. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/diagnostics.py +252 -0
  96. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/emission_checks.py +1006 -0
  97. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/errors.py +163 -0
  98. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/generic_constraint_builder.py +235 -0
  99. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/generic_constraint_format.py +74 -0
  100. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/git.py +40 -0
  101. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/hydro_units.py +135 -0
  102. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/inflow_windows.py +49 -0
  103. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/pandas_utils.py +26 -0
  104. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/paths.py +24 -0
  105. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/penalties.py +204 -0
  106. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/preflight.py +123 -0
  107. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/productivity.py +387 -0
  108. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/provenance.py +131 -0
  109. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/season_calendar.py +53 -0
  110. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/summary_counts.py +32 -0
  111. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/tailrace.py +130 -0
  112. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/tolerances.py +43 -0
  113. novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/units.py +9 -0
  114. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/__init__.py +44 -0
  115. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/chart_helpers.py +529 -0
  116. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/data.py +1410 -0
  117. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/__init__.py +133 -0
  118. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/constraints.py +583 -0
  119. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/constraints_utils.py +151 -0
  120. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/costs.py +1047 -0
  121. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/energy_balance.py +1779 -0
  122. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/network.py +646 -0
  123. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/overview.py +451 -0
  124. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/performance.py +544 -0
  125. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/performance_charts.py +2132 -0
  126. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/plants.py +1276 -0
  127. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/stochastic.py +1661 -0
  128. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/timing_phases.py +73 -0
  129. novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/training.py +631 -0
  130. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/__init__.py +1 -0
  131. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/bounds_accumulator.py +571 -0
  132. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/case.py +128 -0
  133. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/constraint_registers.py +792 -0
  134. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/__init__.py +1 -0
  135. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/anticipated.py +932 -0
  136. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/bounds.py +391 -0
  137. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/__init__.py +62 -0
  138. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/effective.py +562 -0
  139. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/overrides.py +513 -0
  140. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/stage_resolution.py +125 -0
  141. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/config.py +149 -0
  142. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/constraints.py +1126 -0
  143. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/contracts.py +360 -0
  144. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/fpha.py +243 -0
  145. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/__init__.py +61 -0
  146. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/bounds.py +857 -0
  147. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/entity.py +506 -0
  148. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/productivity.py +170 -0
  149. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/libs_electrical.py +2307 -0
  150. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/libs_electrical_emit.py +537 -0
  151. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/ncs.py +437 -0
  152. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/network.py +479 -0
  153. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/scalar_parameters.py +80 -0
  154. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/single_term_bounds.py +539 -0
  155. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/thermal.py +270 -0
  156. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/travel_time.py +169 -0
  157. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/__init__.py +5 -0
  158. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/bootstrap.py +292 -0
  159. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/capability.py +193 -0
  160. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/cortes.py +531 -0
  161. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/importer.py +861 -0
  162. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/mapper.py +852 -0
  163. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/writer.py +228 -0
  164. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/files.py +168 -0
  165. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/group_bounds.py +250 -0
  166. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/id_map.py +136 -0
  167. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/inflow_mlt.py +177 -0
  168. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/load.py +205 -0
  169. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/pipeline.py +1948 -0
  170. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/preflight.py +583 -0
  171. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/scenarios.py +430 -0
  172. novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/temporal.py +417 -0
  173. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/__init__.py +1 -0
  174. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/case.py +300 -0
  175. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/__init__.py +1 -0
  176. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/anticipated.py +241 -0
  177. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/constraints.py +2068 -0
  178. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/fict_cascade.py +285 -0
  179. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/__init__.py +93 -0
  180. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/bounds.py +978 -0
  181. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/entity.py +602 -0
  182. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/geometry.py +377 -0
  183. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/overrides.py +606 -0
  184. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/productivity.py +756 -0
  185. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/inflow_windows.py +124 -0
  186. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/initial_conditions.py +253 -0
  187. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/network.py +1527 -0
  188. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/stochastic.py +886 -0
  189. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/tailrace.py +77 -0
  190. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/temporal.py +660 -0
  191. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/thermal.py +870 -0
  192. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/files.py +261 -0
  193. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/filling.py +297 -0
  194. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/horizon.py +238 -0
  195. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/id_map.py +228 -0
  196. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/pipeline.py +571 -0
  197. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/plants.py +161 -0
  198. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/preflight.py +169 -0
  199. novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/switches.py +157 -0
  200. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/__init__.py +1 -0
  201. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/case_io.py +159 -0
  202. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/case_writer.py +48 -0
  203. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/compat.py +55 -0
  204. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/constraint_expr.py +416 -0
  205. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/cost_categories.py +79 -0
  206. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/readers.py +2075 -0
  207. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/scalar_parameters.py +117 -0
  208. novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/schemas.py +47 -0
  209. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/__init__.py +1 -0
  210. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/compare_summary.py +138 -0
  211. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/console.py +459 -0
  212. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/__init__.py +1 -0
  213. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/css.py +610 -0
  214. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/document.py +310 -0
  215. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/js.py +464 -0
  216. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/plotly.py +360 -0
  217. novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/theme.py +96 -0
  218. novomodelo_bridge-0.18.0/tests/_fcf_fixtures.py +334 -0
  219. novomodelo_bridge-0.18.0/tests/cli/conftest.py +15 -0
  220. novomodelo_bridge-0.18.0/tests/cli/test_cli_app.py +103 -0
  221. novomodelo_bridge-0.18.0/tests/cli/test_cli_args.py +232 -0
  222. novomodelo_bridge-0.18.0/tests/cli/test_cli_check_args.py +525 -0
  223. novomodelo_bridge-0.18.0/tests/cli/test_cli_compare.py +783 -0
  224. novomodelo_bridge-0.18.0/tests/cli/test_cli_compare_args.py +287 -0
  225. novomodelo_bridge-0.18.0/tests/cli/test_cli_compare_failpaths.py +279 -0
  226. novomodelo_bridge-0.18.0/tests/cli/test_cli_convert.py +2641 -0
  227. novomodelo_bridge-0.18.0/tests/cli/test_cli_convert_failpaths.py +197 -0
  228. novomodelo_bridge-0.18.0/tests/cli/test_cli_dashboard_ux.py +456 -0
  229. novomodelo_bridge-0.18.0/tests/cli/test_cli_fail.py +127 -0
  230. novomodelo_bridge-0.18.0/tests/cli/test_config_resolution.py +139 -0
  231. novomodelo_bridge-0.18.0/tests/cli/test_conversion_manifest.py +346 -0
  232. novomodelo_bridge-0.18.0/tests/cli/test_logging_config.py +309 -0
  233. novomodelo_bridge-0.18.0/tests/cli/test_novomodelo_validation.py +36 -0
  234. novomodelo_bridge-0.18.0/tests/cli/test_verdict.py +250 -0
  235. novomodelo_bridge-0.18.0/tests/comparators/conftest.py +337 -0
  236. novomodelo_bridge-0.18.0/tests/comparators/test_alignment.py +195 -0
  237. novomodelo_bridge-0.18.0/tests/comparators/test_analyze.py +1517 -0
  238. novomodelo_bridge-0.18.0/tests/comparators/test_compare.py +1978 -0
  239. novomodelo_bridge-0.18.0/tests/comparators/test_compare_decomp_render.py +443 -0
  240. novomodelo_bridge-0.18.0/tests/comparators/test_compare_results_additions.py +327 -0
  241. novomodelo_bridge-0.18.0/tests/comparators/test_compare_status.py +81 -0
  242. novomodelo_bridge-0.18.0/tests/comparators/test_compare_status_cli.py +217 -0
  243. novomodelo_bridge-0.18.0/tests/comparators/test_comparison_dataset.py +432 -0
  244. novomodelo_bridge-0.18.0/tests/comparators/test_comparison_manifest.py +229 -0
  245. novomodelo_bridge-0.18.0/tests/comparators/test_constraints_compare.py +516 -0
  246. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_export.py +213 -0
  247. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_readers.py +1126 -0
  248. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results.py +816 -0
  249. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_constraints.py +654 -0
  250. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_costs.py +656 -0
  251. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_energy_balance.py +419 -0
  252. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_evaporation.py +441 -0
  253. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_fpha.py +247 -0
  254. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_hydro_thermal_detail.py +407 -0
  255. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_network.py +766 -0
  256. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_performance.py +626 -0
  257. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_productivity.py +295 -0
  258. novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_ree.py +385 -0
  259. novomodelo_bridge-0.18.0/tests/comparators/test_export.py +244 -0
  260. novomodelo_bridge-0.18.0/tests/comparators/test_fpha.py +332 -0
  261. novomodelo_bridge-0.18.0/tests/comparators/test_fpha_comparison.py +658 -0
  262. novomodelo_bridge-0.18.0/tests/comparators/test_golden_dataset.py +132 -0
  263. novomodelo_bridge-0.18.0/tests/comparators/test_newave_readers.py +314 -0
  264. novomodelo_bridge-0.18.0/tests/comparators/test_report_builder.py +502 -0
  265. novomodelo_bridge-0.18.0/tests/comparators/test_retired_decomp_symbols.py +51 -0
  266. novomodelo_bridge-0.18.0/tests/comparators/test_vminop_useful_energy.py +283 -0
  267. novomodelo_bridge-0.18.0/tests/conftest.py +694 -0
  268. novomodelo_bridge-0.18.0/tests/core/test_bound_merge.py +119 -0
  269. novomodelo_bridge-0.18.0/tests/core/test_diagnostics.py +186 -0
  270. novomodelo_bridge-0.18.0/tests/core/test_emission_checks.py +957 -0
  271. novomodelo_bridge-0.18.0/tests/core/test_errors.py +122 -0
  272. novomodelo_bridge-0.18.0/tests/core/test_generic_constraint_builder.py +125 -0
  273. novomodelo_bridge-0.18.0/tests/core/test_generic_constraint_format.py +47 -0
  274. novomodelo_bridge-0.18.0/tests/core/test_pandas_utils.py +30 -0
  275. novomodelo_bridge-0.18.0/tests/core/test_paths.py +25 -0
  276. novomodelo_bridge-0.18.0/tests/core/test_productivity_module.py +187 -0
  277. novomodelo_bridge-0.18.0/tests/core/test_tolerances.py +50 -0
  278. novomodelo_bridge-0.18.0/tests/dashboard/test_chart_helpers.py +1843 -0
  279. novomodelo_bridge-0.18.0/tests/dashboard/test_costs.py +887 -0
  280. novomodelo_bridge-0.18.0/tests/dashboard/test_dashboard.py +2068 -0
  281. novomodelo_bridge-0.18.0/tests/dashboard/test_dashboard_data.py +917 -0
  282. novomodelo_bridge-0.18.0/tests/dashboard/test_energy_balance.py +1013 -0
  283. novomodelo_bridge-0.18.0/tests/dashboard/test_network.py +866 -0
  284. novomodelo_bridge-0.18.0/tests/dashboard/test_overview.py +524 -0
  285. novomodelo_bridge-0.18.0/tests/dashboard/test_performance.py +788 -0
  286. novomodelo_bridge-0.18.0/tests/dashboard/test_plants.py +1148 -0
  287. novomodelo_bridge-0.18.0/tests/dashboard/test_stochastic.py +1286 -0
  288. novomodelo_bridge-0.18.0/tests/dashboard/test_tab_constraints.py +1395 -0
  289. novomodelo_bridge-0.18.0/tests/dashboard/test_tab_ordering.py +33 -0
  290. novomodelo_bridge-0.18.0/tests/dashboard/test_timing_phases.py +180 -0
  291. novomodelo_bridge-0.18.0/tests/dashboard/test_training.py +412 -0
  292. novomodelo_bridge-0.18.0/tests/decks/decomp_mini/caso.dat +3 -0
  293. novomodelo_bridge-0.18.0/tests/decks/decomp_mini/dadger.rv2 +5396 -0
  294. novomodelo_bridge-0.18.0/tests/decks/decomp_mini/hidr.dat +0 -0
  295. novomodelo_bridge-0.18.0/tests/decks/decomp_mini/polinjus.csv +2755 -0
  296. novomodelo_bridge-0.18.0/tests/decks/decomp_mini/rv2 +6 -0
  297. novomodelo_bridge-0.18.0/tests/decks/decomp_mini/vazoes.rv2 +0 -0
  298. novomodelo_bridge-0.18.0/tests/decks/newave_mini/arquivos.dat +45 -0
  299. novomodelo_bridge-0.18.0/tests/decks/newave_mini/caso.dat +1 -0
  300. novomodelo_bridge-0.18.0/tests/decks/newave_mini/clast.dat +271 -0
  301. novomodelo_bridge-0.18.0/tests/decks/newave_mini/confhd.dat +173 -0
  302. novomodelo_bridge-0.18.0/tests/decks/newave_mini/conft.dat +160 -0
  303. novomodelo_bridge-0.18.0/tests/decks/newave_mini/dger.dat +102 -0
  304. novomodelo_bridge-0.18.0/tests/decks/newave_mini/hidr.dat +0 -0
  305. novomodelo_bridge-0.18.0/tests/decks/newave_mini/patamar.dat +804 -0
  306. novomodelo_bridge-0.18.0/tests/decks/newave_mini/polinjus.csv +2705 -0
  307. novomodelo_bridge-0.18.0/tests/decks/newave_mini/ree.dat +17 -0
  308. novomodelo_bridge-0.18.0/tests/decks/newave_mini/sistema.dat +317 -0
  309. novomodelo_bridge-0.18.0/tests/decks/newave_mini/term.dat +160 -0
  310. novomodelo_bridge-0.18.0/tests/decks/newave_mini/vazoes.dat +0 -0
  311. novomodelo_bridge-0.18.0/tests/decomp/test_anticipated.py +896 -0
  312. novomodelo_bridge-0.18.0/tests/decomp/test_bound_contributions.py +628 -0
  313. novomodelo_bridge-0.18.0/tests/decomp/test_bounds_accumulator.py +587 -0
  314. novomodelo_bridge-0.18.0/tests/decomp/test_bounds_column_order.py +103 -0
  315. novomodelo_bridge-0.18.0/tests/decomp/test_cadastro.py +309 -0
  316. novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_calendar.py +87 -0
  317. novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_head.py +576 -0
  318. novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_machine.py +467 -0
  319. novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_topology.py +405 -0
  320. novomodelo_bridge-0.18.0/tests/decomp/test_case.py +200 -0
  321. novomodelo_bridge-0.18.0/tests/decomp/test_constraint_registers.py +622 -0
  322. novomodelo_bridge-0.18.0/tests/decomp/test_constraints.py +848 -0
  323. novomodelo_bridge-0.18.0/tests/decomp/test_contracts.py +901 -0
  324. novomodelo_bridge-0.18.0/tests/decomp/test_convert_decomp_e2e.py +29 -0
  325. novomodelo_bridge-0.18.0/tests/decomp/test_diversion.py +221 -0
  326. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_bootstrap.py +528 -0
  327. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_capability.py +295 -0
  328. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_config.py +71 -0
  329. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_cortes.py +393 -0
  330. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_deck_independence.py +54 -0
  331. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_gnl_roundtrip.py +238 -0
  332. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_importer.py +990 -0
  333. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_injection.py +434 -0
  334. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_mapper.py +820 -0
  335. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_roundtrip.py +445 -0
  336. novomodelo_bridge-0.18.0/tests/decomp/test_fcf_writer.py +225 -0
  337. novomodelo_bridge-0.18.0/tests/decomp/test_fpha.py +295 -0
  338. novomodelo_bridge-0.18.0/tests/decomp/test_group_bounds.py +374 -0
  339. novomodelo_bridge-0.18.0/tests/decomp/test_head_aware_max_turbined.py +423 -0
  340. novomodelo_bridge-0.18.0/tests/decomp/test_hydro_thermal.py +677 -0
  341. novomodelo_bridge-0.18.0/tests/decomp/test_id_map.py +42 -0
  342. novomodelo_bridge-0.18.0/tests/decomp/test_idecomp112.py +269 -0
  343. novomodelo_bridge-0.18.0/tests/decomp/test_inflow_mlt.py +118 -0
  344. novomodelo_bridge-0.18.0/tests/decomp/test_inflow_observations.py +93 -0
  345. novomodelo_bridge-0.18.0/tests/decomp/test_itaipu_min_generation.py +176 -0
  346. novomodelo_bridge-0.18.0/tests/decomp/test_iv_node.py +489 -0
  347. novomodelo_bridge-0.18.0/tests/decomp/test_libs_electrical.py +1594 -0
  348. novomodelo_bridge-0.18.0/tests/decomp/test_libs_electrical_emit.py +985 -0
  349. novomodelo_bridge-0.18.0/tests/decomp/test_libs_electrical_pipeline.py +645 -0
  350. novomodelo_bridge-0.18.0/tests/decomp/test_machine_set_migration.py +292 -0
  351. novomodelo_bridge-0.18.0/tests/decomp/test_ncs.py +197 -0
  352. novomodelo_bridge-0.18.0/tests/decomp/test_network_load.py +354 -0
  353. novomodelo_bridge-0.18.0/tests/decomp/test_pipeline.py +2063 -0
  354. novomodelo_bridge-0.18.0/tests/decomp/test_preflight.py +471 -0
  355. novomodelo_bridge-0.18.0/tests/decomp/test_rhe.py +615 -0
  356. novomodelo_bridge-0.18.0/tests/decomp/test_rq_bounds.py +410 -0
  357. novomodelo_bridge-0.18.0/tests/decomp/test_runofriver_storage.py +340 -0
  358. novomodelo_bridge-0.18.0/tests/decomp/test_scalar_parameters.py +72 -0
  359. novomodelo_bridge-0.18.0/tests/decomp/test_single_term_bounds.py +1252 -0
  360. novomodelo_bridge-0.18.0/tests/decomp/test_storage_bounds.py +275 -0
  361. novomodelo_bridge-0.18.0/tests/decomp/test_temporal.py +341 -0
  362. novomodelo_bridge-0.18.0/tests/decomp/test_travel_time.py +248 -0
  363. novomodelo_bridge-0.18.0/tests/fixtures/constraints_compare/newave/MEDIAS-USIH.CSV +5 -0
  364. novomodelo_bridge-0.18.0/tests/fixtures/constraints_compare/novomodelo_input/constraints/generic_constraint_bounds.parquet +0 -0
  365. novomodelo_bridge-0.18.0/tests/fixtures/constraints_compare/novomodelo_input/constraints/generic_constraints.json +16 -0
  366. novomodelo_bridge-0.18.0/tests/fixtures/decomp_results/dec_oper_gnl.csv +130 -0
  367. novomodelo_bridge-0.18.0/tests/fixtures/decomp_results/dec_oper_ree.csv +124 -0
  368. novomodelo_bridge-0.18.0/tests/fixtures/decomp_results/decomp.tim +239 -0
  369. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-MERC.CSV +3 -0
  370. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-REE.CSV +2 -0
  371. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-SIN.CSV +3 -0
  372. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-USIH.CSV +5 -0
  373. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-USIT.CSV +2 -0
  374. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/c_adic.dat +5 -0
  375. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/fpha_cortes.csv +7 -0
  376. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/fpha_eco.csv +8 -0
  377. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/int001002.out +9 -0
  378. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/mercl001.out +5 -0
  379. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/newave.tim +15 -0
  380. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/pmo.dat +52 -0
  381. novomodelo_bridge-0.18.0/tests/fixtures/newave_results/sistema.dat +26 -0
  382. novomodelo_bridge-0.18.0/tests/fixtures/novomodelo_d41/constraints/contract_bounds.parquet +0 -0
  383. novomodelo_bridge-0.18.0/tests/fixtures/novomodelo_d41/system/energy_contracts.json +25 -0
  384. novomodelo_bridge-0.18.0/tests/fixtures/novomodelo_schemas/energy_contracts.schema.json +129 -0
  385. novomodelo_bridge-0.18.0/tests/golden/build_comparison_report_full.html +2038 -0
  386. novomodelo_bridge-0.18.0/tests/golden/build_hydro_detail_tab.html +1355 -0
  387. novomodelo_bridge-0.18.0/tests/golden/build_thermal_detail_tab.html +107 -0
  388. novomodelo_bridge-0.18.0/tests/golden/dataset_results_summary.json +1 -0
  389. novomodelo_bridge-0.18.0/tests/golden/dataset_results_summary_json.json +12 -0
  390. novomodelo_bridge-0.18.0/tests/golden/dataset_results_tidy.json +1 -0
  391. novomodelo_bridge-0.18.0/tests/golden/hydro_aggregate_chart.html +2 -0
  392. novomodelo_bridge-0.18.0/tests/golden/hydro_per_bus_chart.html +2 -0
  393. novomodelo_bridge-0.18.0/tests/golden/hydro_slack_per_bus_chart.html +2 -0
  394. novomodelo_bridge-0.18.0/tests/golden/line_summary_chart.html +2 -0
  395. novomodelo_bridge-0.18.0/tests/golden/novomodelo_aggregate_chart.html +2 -0
  396. novomodelo_bridge-0.18.0/tests/golden/report_tab_balance.html +1 -0
  397. novomodelo_bridge-0.18.0/tests/golden/report_tab_constraints.html +3 -0
  398. novomodelo_bridge-0.18.0/tests/golden/report_tab_hydro.html +30 -0
  399. novomodelo_bridge-0.18.0/tests/golden/report_tab_hydro_detail.html +1355 -0
  400. novomodelo_bridge-0.18.0/tests/golden/report_tab_network.html +2 -0
  401. novomodelo_bridge-0.18.0/tests/golden/report_tab_overview.html +8 -0
  402. novomodelo_bridge-0.18.0/tests/golden/report_tab_performance.html +7 -0
  403. novomodelo_bridge-0.18.0/tests/golden/report_tab_productivity.html +2 -0
  404. novomodelo_bridge-0.18.0/tests/golden/report_tab_system.html +5 -0
  405. novomodelo_bridge-0.18.0/tests/golden/report_tab_thermal.html +3 -0
  406. novomodelo_bridge-0.18.0/tests/golden/report_tab_thermal_detail.html +107 -0
  407. novomodelo_bridge-0.18.0/tests/golden/system_comparison_chart.html +2 -0
  408. novomodelo_bridge-0.18.0/tests/golden/system_per_bus_chart.html +2 -0
  409. novomodelo_bridge-0.18.0/tests/golden/system_spillage_energy_chart.html +2 -0
  410. novomodelo_bridge-0.18.0/tests/golden/thermal_generation_chart.html +2 -0
  411. novomodelo_bridge-0.18.0/tests/golden_utils.py +74 -0
  412. novomodelo_bridge-0.18.0/tests/newave/conftest.py +629 -0
  413. novomodelo_bridge-0.18.0/tests/newave/test_case.py +210 -0
  414. novomodelo_bridge-0.18.0/tests/newave/test_constraints.py +1908 -0
  415. novomodelo_bridge-0.18.0/tests/newave/test_convert_network.py +807 -0
  416. novomodelo_bridge-0.18.0/tests/newave/test_convert_newave_e2e.py +33 -0
  417. novomodelo_bridge-0.18.0/tests/newave/test_fict_cascade.py +292 -0
  418. novomodelo_bridge-0.18.0/tests/newave/test_files.py +45 -0
  419. novomodelo_bridge-0.18.0/tests/newave/test_filling.py +162 -0
  420. novomodelo_bridge-0.18.0/tests/newave/test_fpha_conversion.py +458 -0
  421. novomodelo_bridge-0.18.0/tests/newave/test_horizon.py +169 -0
  422. novomodelo_bridge-0.18.0/tests/newave/test_hydro_bounds.py +1481 -0
  423. novomodelo_bridge-0.18.0/tests/newave/test_hydro_entity.py +1049 -0
  424. novomodelo_bridge-0.18.0/tests/newave/test_hydro_geometry.py +284 -0
  425. novomodelo_bridge-0.18.0/tests/newave/test_hydro_overrides.py +1073 -0
  426. novomodelo_bridge-0.18.0/tests/newave/test_hydro_productivity.py +1494 -0
  427. novomodelo_bridge-0.18.0/tests/newave/test_id_map.py +404 -0
  428. novomodelo_bridge-0.18.0/tests/newave/test_inflow_windows.py +326 -0
  429. novomodelo_bridge-0.18.0/tests/newave/test_initial_conditions.py +466 -0
  430. novomodelo_bridge-0.18.0/tests/newave/test_pipeline.py +553 -0
  431. novomodelo_bridge-0.18.0/tests/newave/test_plants_module.py +294 -0
  432. novomodelo_bridge-0.18.0/tests/newave/test_preflight.py +201 -0
  433. novomodelo_bridge-0.18.0/tests/newave/test_switches.py +125 -0
  434. novomodelo_bridge-0.18.0/tests/newave/test_tailrace_conversion.py +240 -0
  435. novomodelo_bridge-0.18.0/tests/newave/test_temporal_stochastic_conversion.py +2058 -0
  436. novomodelo_bridge-0.18.0/tests/newave/test_thermal.py +1096 -0
  437. novomodelo_bridge-0.18.0/tests/novomodelo/test_case_writer.py +60 -0
  438. novomodelo_bridge-0.18.0/tests/novomodelo/test_cost_categories.py +58 -0
  439. novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_compat.py +52 -0
  440. novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_io.py +113 -0
  441. novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_readers.py +1673 -0
  442. novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_schemas.py +169 -0
  443. novomodelo_bridge-0.18.0/tests/test_docs.py +63 -0
  444. novomodelo_bridge-0.18.0/tests/test_lineage.py +153 -0
  445. novomodelo_bridge-0.18.0/tests/test_local_data_policy.py +65 -0
  446. novomodelo_bridge-0.18.0/tests/test_package.py +9 -0
  447. novomodelo_bridge-0.18.0/tests/test_package_boundaries.py +420 -0
  448. novomodelo_bridge-0.18.0/tests/test_packaging.py +135 -0
  449. novomodelo_bridge-0.18.0/tests/ui/test_console.py +367 -0
  450. novomodelo_bridge-0.18.0/tests/ui/test_console_dataset_parity.py +311 -0
  451. novomodelo_bridge-0.18.0/tests/ui/test_html_safety.py +90 -0
  452. novomodelo_bridge-0.18.0/tests/ui/test_plotly_helpers.py +244 -0
  453. novomodelo_bridge-0.18.0/tests/ui/test_ui.py +670 -0
  454. novomodelo_bridge-0.18.0/tests/ui/test_ui_updates.py +183 -0
  455. novomodelo_bridge-0.18.0/uv.lock +805 -0
@@ -0,0 +1,97 @@
1
+ ---
2
+ paths:
3
+ - "src/novomodelo_bridge/**/*.py"
4
+ ---
5
+
6
+ # Novomodelo-Bridge Conversion & CLI Contracts
7
+
8
+ Repo-specific architectural contracts. Each is a contract, not a style
9
+ preference — a plausible deviation ships wrong numbers, silent divergence
10
+ between the two conversion tracks, or messages a pip-installed user cannot act
11
+ on. The known standing architectural violations have been catalogued and
12
+ remediated; do not introduce new ones.
13
+
14
+ ## 1. Twin-track symmetry (the central contract)
15
+
16
+ The NEWAVE track (`newave/`, spine + `newave/converters/` +
17
+ `newave/pipeline.py`) and the DECOMP track (`decomp/`, spine +
18
+ `decomp/converters/` + `decomp/pipeline.py`) must honour the **same
19
+ contracts** for the same operations:
20
+
21
+ - A behaviour added to one track — a CLI flag, an exit-code rule, an emission
22
+ self-check, a `--force`/rollback rule, a verdict field, a preflight policy —
23
+ lands on **both tracks in the same change**, or the asymmetry is recorded as
24
+ a finding in the audit registry. Silent one-track divergence is the repo's
25
+ #1 recorded debt source; never extend it.
26
+ - **Never import an underscore-private name across the `newave/` ↔ `decomp/`
27
+ track boundary.** When both tracks need it, promote it to `core/` (the
28
+ shared foundation both tracks may import) — enforced by
29
+ `tests/test_package_boundaries.py` (Rule B,
30
+ `test_no_private_cross_package_imports`) — the way `decomp/preflight.py`
31
+ reuses the public `CheckItem`/`PreflightResult`.
32
+ - Physics and calendar math (cota polynomials, productivity, stage/block
33
+ weighting) live in **one** shared implementation (in `core/`). Two
34
+ implementations that "agree except at the edges" is exactly the bug class
35
+ `compare` exists to detect — in our own tool.
36
+
37
+ ## 2. TRACKED NOVOMODELO-GAP workarounds
38
+
39
+ Never silently adopt a bridge-side workaround for a novomodelo limitation:
40
+
41
+ 1. Emit a log/diagnostic at the workaround site.
42
+ 2. Mark the site with a `TRACKED NOVOMODELO-GAP (Cn)` comment.
43
+ 3. Register the gap with its **removal condition** in novomodelo's
44
+ `conversion-found-improvements` registry (in the novomodelo repo).
45
+
46
+ These comments are **protected contracts** (`.claude/rules/comments.md` §4):
47
+ never delete or reword one away in a cleanup pass. When the gap closes in
48
+ novomodelo, the workaround and its comment leave together. Reference the registry
49
+ by name, never by a machine-local path.
50
+
51
+ ## 3. Source-model prose
52
+
53
+ Comments, docstrings, and test names say **"the source model"** — not
54
+ NEWAVE/CEPEL — when describing the (single) source format being converted.
55
+ The model names stay in identifiers (`NewaveCase`), CLI/display labels, and
56
+ file names (`dger.dat`). Carve-out: comparator/report code whose *subject* is
57
+ the difference between the two models may name both ("NEWAVE uses full
58
+ submarket names; DECOMP abbreviates") — a contrastive statement cannot be
59
+ written generically.
60
+
61
+ ## 4. User-facing message hygiene
62
+
63
+ CLI messages, diagnostics, and remediation hints must be **self-contained**:
64
+ no repo docs paths, ticket codes, branch names, build tooling, or private
65
+ symbols — pip-installed users have no repo checkout. Breadcrumbs for
66
+ maintainers go in comments, not in the message. Guard new remediation strings
67
+ with a no-leak test (the `test_remediation_has_no_repo_internal_references`
68
+ pattern).
69
+
70
+ ## 5. Presentation and I/O boundaries
71
+
72
+ - **Rich lives only in `ui/`.** No other module constructs a Console or
73
+ imports Rich (TYPE_CHECKING imports excepted). Library code never calls
74
+ `print()`/`sys.stdout.write` — user-facing rendering goes through
75
+ `novomodelo_bridge.ui.console`.
76
+ - **Converters emit structured `Diagnostic` objects** (`diagnostics.emit()`),
77
+ never pre-formatted warning strings — the Diagnostic keeps the entity names,
78
+ stages, and values that the rendering layer turns into detail tables.
79
+ - **Case-file writes go through the pipeline's write funnel** (the
80
+ `_write_json`/`_write_parquet` gate that owns `dry_run`/`would_write`) —
81
+ never a bare `json.dump`/`write_text` into the case dir that bypasses the
82
+ dry-run contract.
83
+ - **Reads of novomodelo output route through `novomodelo_readers`/`novomodelo_io`** — do not
84
+ re-open `stages.json`/`lines.json`/parquet partitions with ad-hoc path
85
+ logic; per-block quantities are always weighted by `stages.json`
86
+ `blocks[].hours` through the shared weighting kernels.
87
+
88
+ ## 6. Exit codes and the `--json` envelope
89
+
90
+ - `status == "error"` and a non-zero exit code travel together — a command
91
+ must never print an ERROR verdict and exit 0.
92
+ - Every failure path under `--json` emits exactly one
93
+ `{schema_version, command, status, summary, diagnostics}` envelope on
94
+ stdout via `verdict.build_verdict` — including early CLI-level failures.
95
+ - Diagnostics produced during a command run inside a `diagnostics.collect()`
96
+ sink and land in the envelope's `diagnostics` array — an emit with no sink
97
+ is a bug, not a fallback.
@@ -0,0 +1,194 @@
1
+ ---
2
+ paths:
3
+ - "**/*.py"
4
+ ---
5
+
6
+ # Novomodelo-Bridge Comment & Docstring Rules
7
+
8
+ Governs every `#` comment and docstring in any `.py` file in this repo.
9
+ Python adaptation of novomodelo's comment discipline (`~/git/novomodelo/.claude/rules/comments.md`);
10
+ the Deletion Test and the Four Voices are identical, the directive set is adapted
11
+ to Python and to this repo's domain (file-format conversion with unit traps).
12
+
13
+ **Default is silence.** A comment is a liability — it costs reader attention and
14
+ it can rot away from the code it describes. The job of this rule is to keep the
15
+ small set of comments that prevent real bugs and delete everything else.
16
+
17
+ ---
18
+
19
+ ## 0. The Default-Off Discipline
20
+
21
+ You are writing code with **no comments** until a specific comment earns its
22
+ place against §1. In order, before any comment exists:
23
+
24
+ 1. **Refactor first.** Rename → extract a well-named function/constant →
25
+ introduce a dataclass/enum/NewType. A comment that a better name would carry
26
+ is a refactor you skipped.
27
+ 2. **Relocate.** The why of a _change_ goes in the commit message; a durable
28
+ cross-cutting contract goes in `.claude/rules/*.md` or a module docstring; a
29
+ behavioural fact goes in a **test name**. Inline comments are the residual:
30
+ the line-local trap none of those can carry.
31
+ 3. **Hoist.** A fact repeated across siblings (dataclass fields, dict keys,
32
+ match arms, call sites) is stated **once** at the enclosing scope.
33
+ 4. **Only then comment** — the shortest form that survives the Deletion Test,
34
+ referencing owning symbols instead of copying their formulas or numbers.
35
+
36
+ ### The docstring floor (terse, not absent)
37
+
38
+ Public modules, classes, and functions carry a docstring (the global Python
39
+ rules mandate this). Pay that floor with **one terse purpose line** plus only
40
+ the sections that carry a nugget (`Args:` entries with a unit/shape/ordering
41
+ contract, `Raises:` that callers must handle). Never a blow-by-blow narration
42
+ of the body, and never the same clause restated across sibling functions —
43
+ hoist shared context to the module docstring.
44
+
45
+ ---
46
+
47
+ ## 1. The Deletion Test (the one gate)
48
+
49
+ > **Delete the comment.** Now — reading only the code, would a competent
50
+ > engineer (a) introduce a bug, (b) "simplify" something correct into something
51
+ > wrong, or (c) be unable to recover a fact that lives **outside this file**?
52
+ > **If none → leave it deleted. If any → restore it, cut to the single clause
53
+ > that triggers the "yes".**
54
+
55
+ | If, with the comment deleted, the code… | Verdict |
56
+ | --------------------------------------------------------------- | ---------------------------------------- |
57
+ | would be mis-edited into a bug (wrong-but-running alternative) | **KEEP** — Contract (Voice 1), 1 clause |
58
+ | would be "simplified" by removing a load-bearing choice | **KEEP** — Rationale (Voice 2), 1 clause |
59
+ | loses a fact that lives outside this file (spec, sibling, test) | **KEEP** — pointer by symbol |
60
+ | reads exactly the same to a competent engineer | **DELETE** |
61
+ | would be clearer with a better name / type / extracted fn | **REFACTOR**, don't comment |
62
+ | loses only "how it got here" / what the next line does | **DELETE** (→ git / a test name) |
63
+
64
+ The test applies to **every** comment site — module docstrings, function
65
+ docstrings, function bodies, call sites, dataclass fields. The common bloat
66
+ shapes, all DELETE:
67
+
68
+ - A docstring that narrates its own body ("then for each stage: reads X,
69
+ computes Y, writes Z").
70
+ - A call-site comment naming the function it precedes. If the call is not
71
+ self-documenting, **rename the function**.
72
+ - Pipeline labels (`# Step 1`, `# Phase 3`) — the function names carry the phases.
73
+ - An "explains why this is long" note with no suppression directive attached.
74
+ - The same clause repeated down a function — state it once, delete the echoes.
75
+ - An `Args:` list restating each parameter's name and type — keep only the
76
+ entries carrying a nugget (a unit, an ordering contract, a shape).
77
+
78
+ When in doubt, **delete** and put the thought in the commit message.
79
+
80
+ ---
81
+
82
+ ## 2. The Four Voices — the only comments that survive §1
83
+
84
+ - **Voice 1 — Contract.** A present-tense invariant **+ the wrong-but-running
85
+ alternative it forbids +** the owning symbol. _Exemplar (this repo):_ "must
86
+ stay pandas, never polars — handing `line_bounds` a polars frame makes the
87
+ bounds overlay silently disappear (`line_summary_chart` iterates
88
+ `.iterrows()`)."
89
+ - **Voice 2 — Rationale.** Why a non-obvious choice was made; kept only while
90
+ the wrong simplification is still plausible. _Exemplar:_ "probability-weighted
91
+ mean, not `.mean()` — uniform averaging is wrong on a skewed scenario fan."
92
+ - **Voice 3 — ~~Narrator~~ (NOT ALLOWED).** What the next line does; how it
93
+ used to work; which ticket changed it; project dates. Belongs in names, git,
94
+ or test names — never a shipped comment.
95
+ - **Voice 4 — Intent/Seam.** Why a currently-unused item exists and what
96
+ activates it — pinned to the suppression or guard that anchors it (an unused
97
+ parameter kept for a wired-later caller must say so, or be deleted).
98
+
99
+ ---
100
+
101
+ ## 3. Directives
102
+
103
+ ### DO
104
+
105
+ - **D1 — Contracts.** When the obvious alternative is wrong, say so: invariant +
106
+ forbidden alternative + owning symbol. One clause.
107
+ - **D2 — Units and conventions.** Annotate values carrying physical units or a
108
+ convention trap — this repo is made of them: `m³/s` vs `hm³`, MW vs MWmes/MWh,
109
+ k$ vs R$ (×1000), per-block vs per-stage vs hours-weighted aggregation,
110
+ 1-based source-model ids vs 0-based novomodelo ids, calendar vs stage indexing.
111
+ This is the one comment a good name usually cannot carry, and the direction
112
+ of the conversion ("divide by stage hours, not multiply") is the load-bearing
113
+ clause.
114
+ - **D3 — Rationale above suppression.** Every `# noqa: CODE`,
115
+ `# type: ignore[code]`, `pytest.mark.xfail`, and every deliberate
116
+ `except`-and-degrade carries a reason on the same line or the line above:
117
+ why the fix the diagnostic asks for is inappropriate here. Blanket
118
+ suppressions without a code are banned outright (ruff `PGH` enforces this).
119
+ - **D4 — Data-format traps.** Fixed-width column positions, sentinel values
120
+ (`-1` block ids, `1e31`-style infinities), case-insensitive filename
121
+ resolution, and record-layout quirks of the source-model files are
122
+ out-of-file facts — one clause naming the trap and the format that owns it.
123
+
124
+ ### DON'T
125
+
126
+ - **N1 — No what-narration.** Rename instead.
127
+ - **N2 — No history narration.** No `replaces`/`formerly`, no project-event
128
+ dates, no plan tokens (see N4). Keep the present-tense fact. Carve-outs:
129
+ bibliographic years, calendar/data-coverage years, a deck revision name
130
+ (`mar-26-rv2`) naming a still-existing fixture that pins a contract.
131
+ - **N3 — No drift-prone refs.** Never a source-file line reference — this
132
+ repo's `file.py:NNN` or novomodelo's `file.rs:NNN` / `file.rs ~NN` alike (they
133
+ drift on every edit above the line) — nor commit hashes, `MEMORY.md`,
134
+ `.claude/` paths other than `.claude/rules/*`, machine-local paths
135
+ (`~/git/...`), paths into gitignored dirs (`plans/`), repo-relative paths
136
+ into the novomodelo source tree (`crates/...`, `novomodelo-io/src/...`), or a bare
137
+ internal design-doc section pointer (`design §5`, whose doc lives in
138
+ `plans/`). A pip-installed reader can resolve none of them. Reference by
139
+ symbol, by named test, or by a stable external anchor — the novomodelo book, a
140
+ schema name, a published reference `manual §`, or a novomodelo `file.rs::symbol`
141
+ (all durable, all allowed). For a symbol+line hybrid, keep the symbol, strip
142
+ the line. Gate: `scripts/ci/check_comment_refs.py` (every class hard).
143
+ - **N4 — No plan/workstream leakage** in `src/`, `README.md`, `CHANGELOG.md`,
144
+ or `docs/`. No `epic`/`ticket`/`sprint` vocabulary, no `ticket-NNN`/`epic-NN`
145
+ ids, no acceptance-criteria tags. When a banned token is a trailing tag on a
146
+ contract line, amputate the tag, keep the invariant. Plan refs in **test
147
+ names** stay allowed (identifiers join with `_`, which the gate's word
148
+ boundaries respect). Gate: `scripts/ci/check_no_plan_leaks.py`.
149
+ - **N5 — No banners fencing groups inside one long function** — extract a
150
+ function. Dividers between top-level sections of a large module are fine.
151
+ - **N6 — No duplicated source-of-truth in prose** — a mirror restates the
152
+ _shape_ and references the owner by symbol; it never copies a magic number
153
+ or formula. A drifted mirror is a lie.
154
+
155
+ ### TODO/FIXME
156
+
157
+ A shipped `TODO` carries a durable behavioural tag
158
+ (`TODO(post-study-withdrawal)`) and names the guard or test enforcing the
159
+ current limitation. Never a plan token. Bare ownerless `TODO`s are discouraged.
160
+
161
+ ---
162
+
163
+ ## 4. Protected Contracts (never delete — always considered)
164
+
165
+ A blunt minimization pass that deletes one of these is worse than the bloat it
166
+ removes:
167
+
168
+ - **`TRACKED NOVOMODELO-GAP (Cn)` comments.** Each marks a deliberate workaround for
169
+ a novomodelo limitation, with its removal condition registered in novomodelo's
170
+ `conversion-found-improvements` registry. Never delete or reword one away;
171
+ when the gap closes, the workaround and its comment leave together
172
+ (see `.claude/rules/bridge.md`).
173
+ - **Unit/weighting/direction contracts (D2).** "Weight by block hours",
174
+ "k$ → R$ ×1000", "divide, not multiply" clauses are the difference between
175
+ right and wrong numbers that still render.
176
+ - **Silent-failure warnings (Voice 1)** naming a wrong-but-running alternative
177
+ whose failure mode is invisible (blank chart, empty overlay, zero-vs-zero
178
+ match).
179
+
180
+ Tighten these to one clause + owning symbol; never lose the invariant.
181
+
182
+ ---
183
+
184
+ ## 5. Tooling
185
+
186
+ - `scripts/ci/check_comment_refs.py` — hard gate on N3 (rot-refs), every
187
+ class enforced in `src/` (the pre-existing debt was burned down to zero).
188
+ - `scripts/ci/check_no_plan_leaks.py` — hard gate on N4 in both shipped docs
189
+ and `src/` (the `src/` burndown is complete, so the scope is now enforced,
190
+ not advisory).
191
+ - `scripts/ci/check_comment_bloat.py` — advisory: long comment blocks and
192
+ repeated clauses, candidates for a comment-skeptic pass.
193
+ - `scripts/ci/quality_report.py` — advisory hotspot ranking (churn × size ×
194
+ long functions × suppressions × plan tokens).
@@ -0,0 +1,88 @@
1
+ ---
2
+ paths:
3
+ - "*.md"
4
+ - "docs/**/*.md"
5
+ - ".claude/rules/*.md"
6
+ ---
7
+
8
+ # Novomodelo-Bridge Prose Documentation Integrity Rules
9
+
10
+ Governs every Markdown file that serves as a user-facing or agent-facing
11
+ artifact: `CLAUDE.md`, `README.md`, `CONTRIBUTING.md`, `CHANGELOG.md`,
12
+ `docs/**`, and `.claude/rules/*`. Port of novomodelo's doc-integrity rule; the code-comment
13
+ counterpart is `.claude/rules/comments.md`.
14
+
15
+ ## 1. Reader per doc
16
+
17
+ - **CLAUDE.md / `.claude/rules/*`** — an agent that acts on assertions.
18
+ Durability and anti-drift dominate; a stale claim here is executed, not read.
19
+ - **CHANGELOG.md** — release history. Provenance is *inverted* (history is the
20
+ job); plan-leakage still applies (no epic/ticket tokens in released entries).
21
+ - **README.md / docs/** — newcomers and users, most with **no repo checkout**
22
+ (pip install). Teaching voice is the job; promotional voice is not (§4).
23
+ README.md doubles as the PyPI description, so it links to `docs/` by
24
+ absolute URL.
25
+ - **CONTRIBUTING.md** — a contributor with a checkout. Repo paths are fine;
26
+ every other rule here (durability, voice, plan-leakage) applies unchanged.
27
+ - **`docs/*-data-map.md`** — NEWAVE/DECOMP modellers, in pt-BR. Generated
28
+ from `docs/lineage/*.toml`; the voice and durability rules apply to the TOML
29
+ prose, and the page is never edited by hand.
30
+
31
+ ## 2. The one adaptation that does the heavy lifting
32
+
33
+ > **Name the external contract (filename / flag / config field / path), but
34
+ > never freeze a COUNT, VERSION, ENUMERATION, or run-snapshot NUMBER. Pin any
35
+ > literal that must appear with a guard, not by hand — otherwise state the
36
+ > invariant instead of the number.**
37
+
38
+ Bridge guards that pin literals so prose doesn't have to:
39
+
40
+ - `docs/cli.md` is generated (`scripts/gen-cli-docs.sh`) and content-guarded by
41
+ `tests/test_docs.py` — never hand-edit it.
42
+ - `docs/newave-data-map.md` and `docs/decomp-data-map.md` are generated by
43
+ `scripts/gen-lineage-docs.py` from `docs/lineage/` and gated by
44
+ `tests/test_lineage.py` (freshness, emission coverage against the mini
45
+ decks, cited files and registers exist, code trace). The TOML is the one
46
+ place a converted field's origin is stated; other docs point to the page.
47
+ - `tests/test_packaging.py` holds the `pyproject.toml` `novomodelo-python` pin
48
+ equal to `MIN_NOVOMODELO_VERSION` — prose states the *rule* ("bridge X.Y.Z pairs
49
+ novomodelo X.Y.Z"), never a hand-copied version.
50
+ - Where no guard exists, state the invariant: "every command accepts `--json`"
51
+ (a rule a test can enforce), not "all 7 commands" (a snapshot that rots).
52
+
53
+ ## 3. Failure modes to check on every doc edit
54
+
55
+ 1. **Single-source fan-out.** One fact cached into N docs drifts. One owner per
56
+ fact; other docs carry a shape-only pointer or a guard-pinned literal.
57
+ 2. **Stale current-state snapshots.** A census or completeness claim with no
58
+ adjacent diff to force an edit. State the durable invariant, not the count.
59
+ 3. **Audience-bleed.** Internal symbols/finding-IDs on a user page; repo-only
60
+ paths shown to pip users. Match content to the doc's reader.
61
+ 4. **Unresolvable claims.** Every cited repo-relative path, command, and flag
62
+ must resolve against the live tree (`scripts/ci/check_doc_paths.py` gates
63
+ README/CONTRIBUTING/docs). Never cite gitignored dirs (`plans/`, `example/` decks) or
64
+ machine-local paths (`~/git/...`) in *shipped* docs — the reader cannot
65
+ resolve them. (`example/` is allowed in CLAUDE.md, which documents the
66
+ local-deck convention for this checkout.)
67
+ 5. **Self-executable instructions.** A command a doc tells the reader to run
68
+ must itself succeed — verify by running it.
69
+ 6. **False-confidence guards.** A passing guard is evidence only for the
70
+ fact-class it parses; audit guard *coverage*, not just pass/fail (the
71
+ `uv.lock`-drift-beside-a-green-`test_packaging` incident is the house
72
+ example).
73
+
74
+ ## 4. Voice register — sober reference, not marketing
75
+
76
+ Docs teach; they do not sell. Banned: hype adjectives ("powerful", "robust",
77
+ "seamless", "production-grade"), contrasting-affirmatives ("not just X, it's
78
+ Y"), reader-minimizers ("simply", "just", "easily", "obviously"), and
79
+ unsubstantiated quality claims. The deletion test for voice: delete the
80
+ adjective — if the sentence still states a true, checkable fact, the deletion
81
+ was correct; if it only lost enthusiasm, it was hype.
82
+
83
+ ## 5. Plan-structure leakage
84
+
85
+ No `epic`/`ticket`/`sprint` vocabulary or plan ids in shipped docs — plans live
86
+ in `plans/` (gitignored); public artifacts describe behavior, not how the work
87
+ was organized. Gate: `scripts/ci/check_no_plan_leaks.py`. Commit messages may
88
+ reference plan structure — they target git-log readers.
@@ -0,0 +1,70 @@
1
+ ---
2
+ paths:
3
+ - "tests/**/*.py"
4
+ ---
5
+
6
+ # Novomodelo-Bridge Testing Rules
7
+
8
+ The standing contract for how this repo is tested. Generic Python testing
9
+ style lives in the global Python rules; these are the bridge-specific rules.
10
+
11
+ ## The 2-tier convention (load-bearing — CI depends on it)
12
+
13
+ - **Tier 1 — pure Python.** Imports no `novomodelo`. Runs on every CI job. No
14
+ module at any tier may `import novomodelo` at module scope — the suite must
15
+ *collect* cleanly in a novomodelo-free environment.
16
+ - **Tier 2 — needs `novomodelo-python`** (a required runtime dep) but no solver
17
+ binary. Guarded by `tests.conftest.requires_novomodelo_python`
18
+ (`find_spec`-based) and, where relevant, `requires_writer_binding`; any
19
+ `import novomodelo` lives inside the guarded test/helper body.
20
+
21
+ ## Local-data policy (enforced by `tests/test_local_data_policy.py`)
22
+
23
+ Every test runs from the repository alone:
24
+
25
+ - **No test builds a path into the gitignored `example/` tree, and none
26
+ resolves a path through `Path.home()`.** A test that runs on one developer's
27
+ machine only is a snapshot, not a guard, and its permanent skip elsewhere
28
+ reads as coverage. There is no tier 3.
29
+ - **Real-format inputs are committed as small excerpts** under
30
+ `tests/fixtures/` (result-file heads, a schema, one example case — a few KB
31
+ each); synthetic decks live under `tests/decks/`. Name the excerpt's origin
32
+ in the module docstring.
33
+ - **A check that genuinely needs a whole deck, a solved novomodelo case, or the
34
+ solver binary** is written up in `docs/real-deck-checks.md` (what it
35
+ verified, what it needs) instead of being kept as a skipped test.
36
+
37
+ ## Test architecture
38
+
39
+ - **Mock discipline: patch public entry points only.** Patching an
40
+ underscore-private symbol couples the test to an implementation detail;
41
+ the repo's baseline is ~5 private patches out of ~300 — keep it there.
42
+ Pipeline tests mock converter entry points; CLI error-path tests run as
43
+ subprocess to verify real exit codes.
44
+ - **One home per source module.** New converter/comparator tests go in a
45
+ `test_<module>.py` named for the source module — do not grow the mega-files
46
+ (`test_entity_conversion.py`, `test_decomp_results_compare.py`, `test_cli.py`
47
+ are legacy exceptions scheduled for splitting, not a pattern to extend).
48
+ - **Shared case builders live in `tests/conftest.py`** (or a shared fixtures
49
+ module) — never re-defined per file.
50
+ - **Test output goes to `tmp_path`.** No test writes into the repo tree;
51
+ generated artifacts self-delete.
52
+ - **Golden files** (`tests/golden/`) pin rendered HTML/dataset shapes. A
53
+ deliberate rendering change regenerates them via the documented regeneration
54
+ path — never hand-edit a golden file, and never loosen an assertion to avoid
55
+ regenerating.
56
+ - **CliRunner + Rich:** Typer help/rendering tests run under a dumb terminal
57
+ (`TERM=dumb` fixture) — Rich box-drawing output differs across CI terminals
58
+ and versions.
59
+ - **Logger state:** an autouse fixture snapshots/restores the `novomodelo_bridge`
60
+ logger so a CliRunner invocation cannot leak logging state into a later
61
+ `caplog` test — new tests that reconfigure logging must go through it.
62
+
63
+ ## What a new feature must ship with
64
+
65
+ - A tier-1 test for the pure conversion/analysis logic (synthetic inputs).
66
+ - For a new CLI flag or exit-code path: a CliRunner or subprocess test
67
+ asserting the exit code and, when `--json` is involved, the verdict envelope.
68
+ - For a new output artifact: an emission-shape test (schema/columns), mirrored
69
+ on **both** tracks when the artifact exists on both (see
70
+ `.claude/rules/bridge.md` — twin-track symmetry).
@@ -0,0 +1,2 @@
1
+ # The mechanical rename (rebrand.py tokens, ruff format, import order).
2
+ 3b9c531fd2ec0c0dabd748fb8f0c721bd37abc84
@@ -0,0 +1,61 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main", "develop"]
6
+ pull_request:
7
+ branches: ["main", "develop"]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: ruff + quality gates
16
+ runs-on: ubuntu-latest
17
+ timeout-minutes: 5
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.13"
23
+ # The ruff uv.lock pins, which the pre-commit hook runs from .venv;
24
+ # tests/test_packaging.py fails if the two differ.
25
+ - run: pip install "ruff==0.16.9"
26
+ - run: ruff check .
27
+ - run: ruff format --check .
28
+ # Drift/rot gates — conventions in .claude/rules/, docs in scripts/README.md
29
+ - run: python3 scripts/ci/check_no_plan_leaks.py
30
+ - run: python3 scripts/ci/check_comment_refs.py
31
+ - run: python3 scripts/ci/check_doc_paths.py
32
+
33
+ test:
34
+ name: pytest (Python ${{ matrix.python-version }})
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 10
37
+ strategy:
38
+ matrix:
39
+ python-version: ["3.12", "3.13", "3.14"]
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+ - uses: actions/setup-python@v5
43
+ with:
44
+ python-version: ${{ matrix.python-version }}
45
+ # novomodelo-python is a core runtime dependency, so `.[dev]` pulls it on
46
+ # every Python in the matrix — the tier-2 (`requires_novomodelo_python`)
47
+ # boundary-FCF tests now run on all jobs, not just the primary one.
48
+ - run: pip install -e ".[dev]"
49
+ - run: pytest --tb=short
50
+
51
+ coverage:
52
+ name: coverage (reader/align layer floor)
53
+ runs-on: ubuntu-latest
54
+ timeout-minutes: 10
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ - uses: actions/setup-python@v5
58
+ with:
59
+ python-version: "3.13"
60
+ - run: pip install -e ".[dev]"
61
+ - run: pytest --cov --cov-report=term-missing
@@ -0,0 +1,55 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ concurrency:
8
+ group: release-${{ github.ref }}
9
+ cancel-in-progress: false
10
+
11
+ jobs:
12
+ build:
13
+ name: Build package
14
+ runs-on: ubuntu-latest
15
+ timeout-minutes: 10
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.13"
21
+ - run: pip install build
22
+ - run: python -m build
23
+ - uses: actions/upload-artifact@v4
24
+ with:
25
+ name: dist
26
+ path: dist/
27
+
28
+ test:
29
+ name: pytest (release gate)
30
+ runs-on: ubuntu-latest
31
+ timeout-minutes: 10
32
+ steps:
33
+ - uses: actions/checkout@v4
34
+ - uses: actions/setup-python@v5
35
+ with:
36
+ python-version: "3.13"
37
+ - run: pip install -e ".[dev]"
38
+ - run: pytest --tb=short
39
+
40
+ publish:
41
+ name: Publish to PyPI
42
+ needs: [build, test]
43
+ runs-on: ubuntu-latest
44
+ timeout-minutes: 5
45
+ permissions:
46
+ id-token: write
47
+ environment:
48
+ name: pypi
49
+ url: https://pypi.org/p/novomodelo-bridge
50
+ steps:
51
+ - uses: actions/download-artifact@v4
52
+ with:
53
+ name: dist
54
+ path: dist/
55
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ .eggs/
11
+ .env
12
+ .venv
13
+ env/
14
+ venv/
15
+
16
+ # pytest
17
+ .pytest_cache/
18
+ .coverage
19
+ htmlcov/
20
+
21
+ # ruff
22
+ .ruff_cache/
23
+
24
+ # editors
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+
29
+ # examples
30
+ example/
31
+
32
+ # Local .claude config is ignored, except the path-scoped quality rules,
33
+ # which are tracked and shared with the team.
34
+ .claude/*
35
+ !.claude/rules/
36
+
37
+ plans/
38
+ CLAUDE.md.tmp
39
+ .mypy_cache/