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.
- novomodelo_bridge-0.18.0/.claude/rules/bridge.md +97 -0
- novomodelo_bridge-0.18.0/.claude/rules/comments.md +194 -0
- novomodelo_bridge-0.18.0/.claude/rules/doc-integrity.md +88 -0
- novomodelo_bridge-0.18.0/.claude/rules/testing.md +70 -0
- novomodelo_bridge-0.18.0/.git-blame-ignore-revs +2 -0
- novomodelo_bridge-0.18.0/.github/workflows/ci.yml +61 -0
- novomodelo_bridge-0.18.0/.github/workflows/release.yml +55 -0
- novomodelo_bridge-0.18.0/.gitignore +39 -0
- novomodelo_bridge-0.18.0/CHANGELOG.md +1160 -0
- novomodelo_bridge-0.18.0/CLAUDE.md +82 -0
- novomodelo_bridge-0.18.0/CONTRIBUTING.md +150 -0
- novomodelo_bridge-0.18.0/LICENSE +203 -0
- novomodelo_bridge-0.18.0/NOTICE +26 -0
- novomodelo_bridge-0.18.0/PKG-INFO +199 -0
- novomodelo_bridge-0.18.0/README.md +174 -0
- novomodelo_bridge-0.18.0/docs/architecture.md +137 -0
- novomodelo_bridge-0.18.0/docs/cli.md +283 -0
- novomodelo_bridge-0.18.0/docs/decomp-data-map.md +2115 -0
- novomodelo_bridge-0.18.0/docs/decomp.md +98 -0
- novomodelo_bridge-0.18.0/docs/lineage/decomp.toml +3271 -0
- novomodelo_bridge-0.18.0/docs/lineage/newave.toml +3269 -0
- novomodelo_bridge-0.18.0/docs/newave-data-map.md +1823 -0
- novomodelo_bridge-0.18.0/docs/newave.md +119 -0
- novomodelo_bridge-0.18.0/docs/real-deck-checks.md +133 -0
- novomodelo_bridge-0.18.0/pyproject.toml +80 -0
- novomodelo_bridge-0.18.0/pyrightconfig.json +27 -0
- novomodelo_bridge-0.18.0/ruff.toml +18 -0
- novomodelo_bridge-0.18.0/scripts/README.md +38 -0
- novomodelo_bridge-0.18.0/scripts/analyze_results.py +469 -0
- novomodelo_bridge-0.18.0/scripts/ci/_scan.py +63 -0
- novomodelo_bridge-0.18.0/scripts/ci/check_comment_bloat.py +89 -0
- novomodelo_bridge-0.18.0/scripts/ci/check_comment_refs.py +106 -0
- novomodelo_bridge-0.18.0/scripts/ci/check_doc_paths.py +143 -0
- novomodelo_bridge-0.18.0/scripts/ci/check_no_plan_leaks.py +114 -0
- novomodelo_bridge-0.18.0/scripts/ci/quality_report.py +149 -0
- novomodelo_bridge-0.18.0/scripts/gen-cli-docs.sh +17 -0
- novomodelo_bridge-0.18.0/scripts/gen-lineage-docs.py +96 -0
- novomodelo_bridge-0.18.0/scripts/lineage/__init__.py +8 -0
- novomodelo_bridge-0.18.0/scripts/lineage/model.py +603 -0
- novomodelo_bridge-0.18.0/scripts/lineage/render.py +422 -0
- novomodelo_bridge-0.18.0/scripts/lineage/trace.py +274 -0
- novomodelo_bridge-0.18.0/scripts/pre-commit +30 -0
- novomodelo_bridge-0.18.0/scripts/regen-goldens.sh +16 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/__init__.py +3 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/__init__.py +7 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/__main__.py +4 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/app.py +511 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/args.py +116 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/check.py +73 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/compare.py +399 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/config.py +225 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/conversion_manifest.py +175 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/convert.py +604 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/dashboard.py +90 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/failure.py +60 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/logging_config.py +105 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/validate.py +193 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/cli/verdict.py +240 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/analyze.py +1827 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/__init__.py +106 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/_shared.py +356 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/constraints.py +190 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/convergence.py +99 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/costs.py +544 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/fpha.py +297 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/hydro.py +515 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/network.py +213 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/overview.py +84 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/performance.py +252 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/productivity.py +284 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/spillage.py +115 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/system.py +252 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/charts/thermal.py +59 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/constraints.py +210 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/dataset.py +570 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/decomp/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/decomp/readers.py +410 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/decomp/results.py +3121 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/export.py +208 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/fpha.py +169 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/html_report.py +69 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/manifest.py +94 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/model.py +265 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/alignment.py +189 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/constraints.py +471 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/readers.py +1094 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/newave/results.py +1311 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/report_builder.py +1234 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/comparators/verdict.py +98 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/bound_merge.py +69 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/conversion.py +66 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/diagnostics.py +252 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/emission_checks.py +1006 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/errors.py +163 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/generic_constraint_builder.py +235 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/generic_constraint_format.py +74 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/git.py +40 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/hydro_units.py +135 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/inflow_windows.py +49 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/pandas_utils.py +26 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/paths.py +24 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/penalties.py +204 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/preflight.py +123 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/productivity.py +387 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/provenance.py +131 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/season_calendar.py +53 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/summary_counts.py +32 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/tailrace.py +130 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/tolerances.py +43 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/core/units.py +9 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/__init__.py +44 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/chart_helpers.py +529 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/data.py +1410 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/__init__.py +133 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/constraints.py +583 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/constraints_utils.py +151 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/costs.py +1047 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/energy_balance.py +1779 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/network.py +646 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/overview.py +451 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/performance.py +544 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/performance_charts.py +2132 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/plants.py +1276 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/stochastic.py +1661 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/timing_phases.py +73 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/dashboard/tabs/training.py +631 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/bounds_accumulator.py +571 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/case.py +128 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/constraint_registers.py +792 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/anticipated.py +932 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/bounds.py +391 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/__init__.py +62 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/effective.py +562 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/overrides.py +513 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/cadastro/stage_resolution.py +125 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/config.py +149 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/constraints.py +1126 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/contracts.py +360 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/fpha.py +243 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/__init__.py +61 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/bounds.py +857 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/entity.py +506 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/hydro/productivity.py +170 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/libs_electrical.py +2307 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/libs_electrical_emit.py +537 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/ncs.py +437 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/network.py +479 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/scalar_parameters.py +80 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/single_term_bounds.py +539 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/thermal.py +270 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/converters/travel_time.py +169 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/__init__.py +5 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/bootstrap.py +292 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/capability.py +193 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/cortes.py +531 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/importer.py +861 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/mapper.py +852 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/fcf/writer.py +228 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/files.py +168 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/group_bounds.py +250 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/id_map.py +136 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/inflow_mlt.py +177 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/load.py +205 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/pipeline.py +1948 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/preflight.py +583 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/scenarios.py +430 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/decomp/temporal.py +417 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/case.py +300 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/anticipated.py +241 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/constraints.py +2068 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/fict_cascade.py +285 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/__init__.py +93 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/bounds.py +978 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/entity.py +602 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/geometry.py +377 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/overrides.py +606 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/hydro/productivity.py +756 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/inflow_windows.py +124 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/initial_conditions.py +253 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/network.py +1527 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/stochastic.py +886 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/tailrace.py +77 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/temporal.py +660 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/converters/thermal.py +870 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/files.py +261 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/filling.py +297 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/horizon.py +238 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/id_map.py +228 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/pipeline.py +571 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/plants.py +161 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/preflight.py +169 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/newave/switches.py +157 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/case_io.py +159 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/case_writer.py +48 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/compat.py +55 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/constraint_expr.py +416 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/cost_categories.py +79 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/readers.py +2075 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/scalar_parameters.py +117 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/novomodelo/schemas.py +47 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/compare_summary.py +138 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/console.py +459 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/__init__.py +1 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/css.py +610 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/document.py +310 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/js.py +464 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/html/plotly.py +360 -0
- novomodelo_bridge-0.18.0/src/novomodelo_bridge/ui/theme.py +96 -0
- novomodelo_bridge-0.18.0/tests/_fcf_fixtures.py +334 -0
- novomodelo_bridge-0.18.0/tests/cli/conftest.py +15 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_app.py +103 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_args.py +232 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_check_args.py +525 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_compare.py +783 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_compare_args.py +287 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_compare_failpaths.py +279 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_convert.py +2641 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_convert_failpaths.py +197 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_dashboard_ux.py +456 -0
- novomodelo_bridge-0.18.0/tests/cli/test_cli_fail.py +127 -0
- novomodelo_bridge-0.18.0/tests/cli/test_config_resolution.py +139 -0
- novomodelo_bridge-0.18.0/tests/cli/test_conversion_manifest.py +346 -0
- novomodelo_bridge-0.18.0/tests/cli/test_logging_config.py +309 -0
- novomodelo_bridge-0.18.0/tests/cli/test_novomodelo_validation.py +36 -0
- novomodelo_bridge-0.18.0/tests/cli/test_verdict.py +250 -0
- novomodelo_bridge-0.18.0/tests/comparators/conftest.py +337 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_alignment.py +195 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_analyze.py +1517 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_compare.py +1978 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_compare_decomp_render.py +443 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_compare_results_additions.py +327 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_compare_status.py +81 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_compare_status_cli.py +217 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_comparison_dataset.py +432 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_comparison_manifest.py +229 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_constraints_compare.py +516 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_export.py +213 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_readers.py +1126 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results.py +816 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_constraints.py +654 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_costs.py +656 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_energy_balance.py +419 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_evaporation.py +441 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_fpha.py +247 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_hydro_thermal_detail.py +407 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_network.py +766 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_performance.py +626 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_productivity.py +295 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_decomp_results_ree.py +385 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_export.py +244 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_fpha.py +332 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_fpha_comparison.py +658 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_golden_dataset.py +132 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_newave_readers.py +314 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_report_builder.py +502 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_retired_decomp_symbols.py +51 -0
- novomodelo_bridge-0.18.0/tests/comparators/test_vminop_useful_energy.py +283 -0
- novomodelo_bridge-0.18.0/tests/conftest.py +694 -0
- novomodelo_bridge-0.18.0/tests/core/test_bound_merge.py +119 -0
- novomodelo_bridge-0.18.0/tests/core/test_diagnostics.py +186 -0
- novomodelo_bridge-0.18.0/tests/core/test_emission_checks.py +957 -0
- novomodelo_bridge-0.18.0/tests/core/test_errors.py +122 -0
- novomodelo_bridge-0.18.0/tests/core/test_generic_constraint_builder.py +125 -0
- novomodelo_bridge-0.18.0/tests/core/test_generic_constraint_format.py +47 -0
- novomodelo_bridge-0.18.0/tests/core/test_pandas_utils.py +30 -0
- novomodelo_bridge-0.18.0/tests/core/test_paths.py +25 -0
- novomodelo_bridge-0.18.0/tests/core/test_productivity_module.py +187 -0
- novomodelo_bridge-0.18.0/tests/core/test_tolerances.py +50 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_chart_helpers.py +1843 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_costs.py +887 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_dashboard.py +2068 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_dashboard_data.py +917 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_energy_balance.py +1013 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_network.py +866 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_overview.py +524 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_performance.py +788 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_plants.py +1148 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_stochastic.py +1286 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_tab_constraints.py +1395 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_tab_ordering.py +33 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_timing_phases.py +180 -0
- novomodelo_bridge-0.18.0/tests/dashboard/test_training.py +412 -0
- novomodelo_bridge-0.18.0/tests/decks/decomp_mini/caso.dat +3 -0
- novomodelo_bridge-0.18.0/tests/decks/decomp_mini/dadger.rv2 +5396 -0
- novomodelo_bridge-0.18.0/tests/decks/decomp_mini/hidr.dat +0 -0
- novomodelo_bridge-0.18.0/tests/decks/decomp_mini/polinjus.csv +2755 -0
- novomodelo_bridge-0.18.0/tests/decks/decomp_mini/rv2 +6 -0
- novomodelo_bridge-0.18.0/tests/decks/decomp_mini/vazoes.rv2 +0 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/arquivos.dat +45 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/caso.dat +1 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/clast.dat +271 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/confhd.dat +173 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/conft.dat +160 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/dger.dat +102 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/hidr.dat +0 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/patamar.dat +804 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/polinjus.csv +2705 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/ree.dat +17 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/sistema.dat +317 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/term.dat +160 -0
- novomodelo_bridge-0.18.0/tests/decks/newave_mini/vazoes.dat +0 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_anticipated.py +896 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_bound_contributions.py +628 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_bounds_accumulator.py +587 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_bounds_column_order.py +103 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_cadastro.py +309 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_calendar.py +87 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_head.py +576 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_machine.py +467 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_cadastro_topology.py +405 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_case.py +200 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_constraint_registers.py +622 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_constraints.py +848 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_contracts.py +901 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_convert_decomp_e2e.py +29 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_diversion.py +221 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_bootstrap.py +528 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_capability.py +295 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_config.py +71 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_cortes.py +393 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_deck_independence.py +54 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_gnl_roundtrip.py +238 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_importer.py +990 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_injection.py +434 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_mapper.py +820 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_roundtrip.py +445 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fcf_writer.py +225 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_fpha.py +295 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_group_bounds.py +374 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_head_aware_max_turbined.py +423 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_hydro_thermal.py +677 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_id_map.py +42 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_idecomp112.py +269 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_inflow_mlt.py +118 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_inflow_observations.py +93 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_itaipu_min_generation.py +176 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_iv_node.py +489 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_libs_electrical.py +1594 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_libs_electrical_emit.py +985 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_libs_electrical_pipeline.py +645 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_machine_set_migration.py +292 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_ncs.py +197 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_network_load.py +354 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_pipeline.py +2063 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_preflight.py +471 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_rhe.py +615 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_rq_bounds.py +410 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_runofriver_storage.py +340 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_scalar_parameters.py +72 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_single_term_bounds.py +1252 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_storage_bounds.py +275 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_temporal.py +341 -0
- novomodelo_bridge-0.18.0/tests/decomp/test_travel_time.py +248 -0
- novomodelo_bridge-0.18.0/tests/fixtures/constraints_compare/newave/MEDIAS-USIH.CSV +5 -0
- novomodelo_bridge-0.18.0/tests/fixtures/constraints_compare/novomodelo_input/constraints/generic_constraint_bounds.parquet +0 -0
- novomodelo_bridge-0.18.0/tests/fixtures/constraints_compare/novomodelo_input/constraints/generic_constraints.json +16 -0
- novomodelo_bridge-0.18.0/tests/fixtures/decomp_results/dec_oper_gnl.csv +130 -0
- novomodelo_bridge-0.18.0/tests/fixtures/decomp_results/dec_oper_ree.csv +124 -0
- novomodelo_bridge-0.18.0/tests/fixtures/decomp_results/decomp.tim +239 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-MERC.CSV +3 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-REE.CSV +2 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-SIN.CSV +3 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-USIH.CSV +5 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/MEDIAS-USIT.CSV +2 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/c_adic.dat +5 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/fpha_cortes.csv +7 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/fpha_eco.csv +8 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/int001002.out +9 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/mercl001.out +5 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/newave.tim +15 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/pmo.dat +52 -0
- novomodelo_bridge-0.18.0/tests/fixtures/newave_results/sistema.dat +26 -0
- novomodelo_bridge-0.18.0/tests/fixtures/novomodelo_d41/constraints/contract_bounds.parquet +0 -0
- novomodelo_bridge-0.18.0/tests/fixtures/novomodelo_d41/system/energy_contracts.json +25 -0
- novomodelo_bridge-0.18.0/tests/fixtures/novomodelo_schemas/energy_contracts.schema.json +129 -0
- novomodelo_bridge-0.18.0/tests/golden/build_comparison_report_full.html +2038 -0
- novomodelo_bridge-0.18.0/tests/golden/build_hydro_detail_tab.html +1355 -0
- novomodelo_bridge-0.18.0/tests/golden/build_thermal_detail_tab.html +107 -0
- novomodelo_bridge-0.18.0/tests/golden/dataset_results_summary.json +1 -0
- novomodelo_bridge-0.18.0/tests/golden/dataset_results_summary_json.json +12 -0
- novomodelo_bridge-0.18.0/tests/golden/dataset_results_tidy.json +1 -0
- novomodelo_bridge-0.18.0/tests/golden/hydro_aggregate_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/hydro_per_bus_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/hydro_slack_per_bus_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/line_summary_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/novomodelo_aggregate_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_balance.html +1 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_constraints.html +3 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_hydro.html +30 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_hydro_detail.html +1355 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_network.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_overview.html +8 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_performance.html +7 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_productivity.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_system.html +5 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_thermal.html +3 -0
- novomodelo_bridge-0.18.0/tests/golden/report_tab_thermal_detail.html +107 -0
- novomodelo_bridge-0.18.0/tests/golden/system_comparison_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/system_per_bus_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/system_spillage_energy_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden/thermal_generation_chart.html +2 -0
- novomodelo_bridge-0.18.0/tests/golden_utils.py +74 -0
- novomodelo_bridge-0.18.0/tests/newave/conftest.py +629 -0
- novomodelo_bridge-0.18.0/tests/newave/test_case.py +210 -0
- novomodelo_bridge-0.18.0/tests/newave/test_constraints.py +1908 -0
- novomodelo_bridge-0.18.0/tests/newave/test_convert_network.py +807 -0
- novomodelo_bridge-0.18.0/tests/newave/test_convert_newave_e2e.py +33 -0
- novomodelo_bridge-0.18.0/tests/newave/test_fict_cascade.py +292 -0
- novomodelo_bridge-0.18.0/tests/newave/test_files.py +45 -0
- novomodelo_bridge-0.18.0/tests/newave/test_filling.py +162 -0
- novomodelo_bridge-0.18.0/tests/newave/test_fpha_conversion.py +458 -0
- novomodelo_bridge-0.18.0/tests/newave/test_horizon.py +169 -0
- novomodelo_bridge-0.18.0/tests/newave/test_hydro_bounds.py +1481 -0
- novomodelo_bridge-0.18.0/tests/newave/test_hydro_entity.py +1049 -0
- novomodelo_bridge-0.18.0/tests/newave/test_hydro_geometry.py +284 -0
- novomodelo_bridge-0.18.0/tests/newave/test_hydro_overrides.py +1073 -0
- novomodelo_bridge-0.18.0/tests/newave/test_hydro_productivity.py +1494 -0
- novomodelo_bridge-0.18.0/tests/newave/test_id_map.py +404 -0
- novomodelo_bridge-0.18.0/tests/newave/test_inflow_windows.py +326 -0
- novomodelo_bridge-0.18.0/tests/newave/test_initial_conditions.py +466 -0
- novomodelo_bridge-0.18.0/tests/newave/test_pipeline.py +553 -0
- novomodelo_bridge-0.18.0/tests/newave/test_plants_module.py +294 -0
- novomodelo_bridge-0.18.0/tests/newave/test_preflight.py +201 -0
- novomodelo_bridge-0.18.0/tests/newave/test_switches.py +125 -0
- novomodelo_bridge-0.18.0/tests/newave/test_tailrace_conversion.py +240 -0
- novomodelo_bridge-0.18.0/tests/newave/test_temporal_stochastic_conversion.py +2058 -0
- novomodelo_bridge-0.18.0/tests/newave/test_thermal.py +1096 -0
- novomodelo_bridge-0.18.0/tests/novomodelo/test_case_writer.py +60 -0
- novomodelo_bridge-0.18.0/tests/novomodelo/test_cost_categories.py +58 -0
- novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_compat.py +52 -0
- novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_io.py +113 -0
- novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_readers.py +1673 -0
- novomodelo_bridge-0.18.0/tests/novomodelo/test_novomodelo_schemas.py +169 -0
- novomodelo_bridge-0.18.0/tests/test_docs.py +63 -0
- novomodelo_bridge-0.18.0/tests/test_lineage.py +153 -0
- novomodelo_bridge-0.18.0/tests/test_local_data_policy.py +65 -0
- novomodelo_bridge-0.18.0/tests/test_package.py +9 -0
- novomodelo_bridge-0.18.0/tests/test_package_boundaries.py +420 -0
- novomodelo_bridge-0.18.0/tests/test_packaging.py +135 -0
- novomodelo_bridge-0.18.0/tests/ui/test_console.py +367 -0
- novomodelo_bridge-0.18.0/tests/ui/test_console_dataset_parity.py +311 -0
- novomodelo_bridge-0.18.0/tests/ui/test_html_safety.py +90 -0
- novomodelo_bridge-0.18.0/tests/ui/test_plotly_helpers.py +244 -0
- novomodelo_bridge-0.18.0/tests/ui/test_ui.py +670 -0
- novomodelo_bridge-0.18.0/tests/ui/test_ui_updates.py +183 -0
- 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,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/
|