[build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "co_study4grid" version = "0.9.0" authors = [ { name="RTE", email="rte@rte-france.com" }, ] description = "Co-Study4Grid — power grid contingency analysis interface" readme = "README.md" requires-python = ">=3.10" license = "MPL-2.0" classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Operating System :: OS Independent", ] dependencies = [ "ExpertOp4Grid>=0.3.2.post1", # Pinned below 1.15 until the test baseline # (`expert_backend/tests/baseline_scenario.json`) is regenerated with # the newer solver. pypowsybl 1.15.0 shifts both P (~±2.6 MW) and Q # (sign-flipping +1.8 → −10.2 Mvar on COUCHY632) on double-busbar # node_merging actions, breaking the ±1 MW tolerance of # `test_independent_actions_simulation`. See PR #99 discussion. "pypowsybl>=1.13.0,<1.15", "pypowsybl_jupyter", "pydantic-settings", "uv", "fastapi", "uvicorn", "python-multipart", # Required by the overflow-graph rendering pipeline. ``pydot`` is the # Python wrapper; the ``dot`` system binary it shells out to is # installed best-effort by ``setup.py`` via ``scripts/install_graphviz.py`` # (see also the ``costudy4grid-install-graphviz`` console script). "pydot", ] [project.optional-dependencies] test = [ "pytest", "pytest-cov", "httpx", "lxml", "pandas", ] quality = [ "ruff>=0.6", # radon is no longer required by the gate (cyclomatic complexity + # nesting are computed from the AST in scripts/code_quality_report.py); # kept for ad-hoc `radon cc` exploration. "radon>=6.0", # Pinned to a minor for CI reproducibility now that mypy GATES the build # (a newer minor could surface new diagnostics and break an unrelated PR). "mypy==1.19.*", ] [project.scripts] # Manual fallback when the post-install hook can't elevate to install # the Graphviz ``dot`` binary on a user's machine. costudy4grid-install-graphviz = "expert_backend.install_graphviz:ensure_dot" [tool.setuptools.packages.find] where = ["."] include = ["expert_backend*"] exclude = ["frontend*", "tests*", "venv*"] # --- Code-quality tooling --------------------------------------------------- # Ruff is scoped to a light, non-stylistic ruleset: we catch real bugs # (undefined names, unused imports, common bug patterns) and leave formatting # choices alone until the team adopts a formatter. [tool.ruff] line-length = 120 target-version = "py310" extend-exclude = [ "scripts/parity_e2e", # Ad-hoc data-prep scripts that rely on top-level `exec()` chaining — # ruff cannot resolve names introduced by the exec'd child script. "scripts/add_limits_and_overloads.py", "scripts/add_detailed_topology.py", "Overflow_Graph", "frontend", "standalone_interface_legacy.html", ] [tool.ruff.lint] # Start strict-but-narrow: only real bugs gate CI today. Broader # ruleset (`B`, `W`, `UP`) is available via # `ruff check --select E9,F,B,W,UP` for local clean-up work. select = [ "E9", # syntax errors "F", # pyflakes (undefined names, unused imports, etc.) ] ignore = [ "E501", # line length is tracked as a metric, not a blocker "F401", # test / scripts modules have legitimate `import X # noqa` fixtures "F841", # debug/inspection scripts intentionally keep unused locals ] [tool.ruff.lint.per-file-ignores] "expert_backend/tests/*" = ["F811"] # pytest fixture redefinitions "scripts/*" = [] # --- mypy (GATING) ---------------------------------------------------------- # The shared RecommenderService surface is declared in # services/_recommender_state.py (a TYPE_CHECKING-only base the mixins inherit # at type-check time, `object` at runtime), so per-class checking no longer # false-positives on the mixin composition. mypy is clean (0 errors) and now # GATES the build in CI. See docs/architecture/code-quality-analysis.md §19. [tool.mypy] files = ["expert_backend"] exclude = '(^|/)(tests/|test_backend\.py|install_graphviz\.py)' ignore_missing_imports = true warn_unused_configs = true # Lenient: advisory visibility, not strictness. Tighten incrementally. check_untyped_defs = false disallow_untyped_defs = false # The pluggable-model layer deliberately re-binds service methods per active # model (its whole job), so `method-assign` is expected there — scope-disable # it for that one module rather than sprinkling `# type: ignore`. [[tool.mypy.overrides]] module = "expert_backend.recommenders._service_integration" disable_error_code = ["method-assign"] # --- coverage (GATED) ------------------------------------------------------- # Floor read off the first green CI run (backend was 78% on the no-graphviz # job, 849 passed). `fail_under` sits 6 points below at 72 so a routine PR # passes but coverage can't erode; ratchet up as it climbs. Enforced wherever # `pytest --cov` runs (CI test jobs); can't be measured offline (the real # recommender is needed). See §20. [tool.coverage.run] source = ["expert_backend"] omit = [ "expert_backend/tests/*", "expert_backend/test_backend.py", "expert_backend/install_graphviz.py", ] [tool.coverage.report] fail_under = 72