Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ name: actions

run-name: RePyability CI

# The one automated test run: the full suite on a pull request into master
# The one automated test run: the full suite on a pull request into main
# (from dev). Nothing else runs it; release.yml publishes only what it
# passed.
on:
pull_request:
branches: [master]
branches: [main]

jobs:
lint:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/close-issues.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ name: close issues

# Close the issues a pull request into dev finishes, when it merges.
# GitHub itself closes an issue only when the pull request that says
# "Closes #N" merges into the default branch (master), which here happens
# "Closes #N" merges into the default branch (main), which here happens
# at a release: this closes them as the work reaches dev, with a comment
# naming the pull request. It runs no tests (the one automated test run
# is actions.yml, on the pull request from dev into master).
# is actions.yml, on the pull request from dev into main).

on:
pull_request:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: docs

# Build the MkDocs site and publish it to GitHub Pages on every push to
# master. Pull requests are validated by the strict docs build in
# main. Pull requests are validated by the strict docs build in
# actions.yml; this workflow does the actual deployment.
#
# One-time repository setup (Settings -> Pages -> Build and deployment):
# set the Source to "GitHub Actions". No gh-pages branch is used.

on:
push:
branches: [master]
branches: [main]
workflow_dispatch:

# Grant the GITHUB_TOKEN the permissions the Pages deployment needs.
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ name: release

# Publishes the package to PyPI, in either of two ways.
#
# 1. Run this workflow by hand on master (Actions -> release -> Run workflow;
# `gh workflow run release.yml --ref master -f version=X.Y`; or the REST
# 1. Run this workflow by hand on main (Actions -> release -> Run workflow;
# `gh workflow run release.yml --ref main -f version=X.Y`; or the REST
# API). It checks that the version typed in matches
# repyability/_version.py, that CHANGELOG.md has a dated section for it,
# that the tag vX.Y does not exist yet, that the full suite passed on
# these files (on the pull request from dev into master) and
# these files (on the pull request from dev into main) and
# that PyPI does not have the version. It then builds, publishes to PyPI,
# and last creates the tag vX.Y and a GitHub Release. The release notes
# open with the CHANGELOG section's summary, followed by GitHub's
Expand Down Expand Up @@ -71,8 +71,8 @@ jobs:
echo "::error::'$REQUESTED' isn't major.minor: versions have two parts (e.g. 0.11)."
exit 1
fi
if [ "$GITHUB_REF" != "refs/heads/master" ]; then
echo "::error::Releases are cut from master, not $GITHUB_REF."
if [ "$GITHUB_REF" != "refs/heads/main" ]; then
echo "::error::Releases are cut from main, not $GITHUB_REF."
exit 1
fi
version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' repyability/_version.py)
Expand All @@ -88,9 +88,9 @@ jobs:
echo "::error::The tag v$version already exists."
exit 1
fi
# The full suite runs on the pull request from dev into master
# The full suite runs on the pull request from dev into main
# (actions.yml), whose run is recorded against dev's commit. The
# merge commit on master holds the same files, so a pass on this
# merge commit on main holds the same files, so a pass on this
# commit, or on a parent with the same files, counts.
repo="repos/$GITHUB_REPOSITORY"
tree=$(gh api "$repo/git/commits/$GITHUB_SHA" --jq '.tree.sha')
Expand All @@ -104,7 +104,7 @@ jobs:
fi
done
if [ -z "$passed" ]; then
echo "::error::The full suite (actions.yml) has not passed on these files: on $GITHUB_SHA or the dev commit it merged. Open the pull request from dev into master and wait for it."
echo "::error::The full suite (actions.yml) has not passed on these files: on $GITHUB_SHA or the dev commit it merged. Open the pull request from dev into main and wait for it."
exit 1
fi
echo "The full suite passed on $passed, with the same files as $GITHUB_SHA."
Expand Down
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@ other release, fixes included, the minor.

## [Unreleased]

## [0.15] - 2026-10-17

surpyval 0.25, and a joint model in a diagram. A unit of a surpyval joint
model of readings and failures (`surpyval.WeibullJointPH`), given its
readings so far, is a block of a `NonRepairableRBD` (`JointNode`, or its
`DynamicPrediction` given as the node): its reliability is from now, and
it takes part in every analysis of the diagram. A `RepairableRBD` refuses
it, as the units that replace it are new and without its readings. What
0.14 deprecated is gone.

Behaviour changes: surpyval 0.25 is required; `NodeState`'s `phase` is
refused (it is `since_scheduled`).

### Added

- A unit of a surpyval joint model of readings and failures, given its
readings, is a block of a `NonRepairableRBD`: a `JointNode`, or the
unit's `DynamicPrediction` (`joint_model.predict(Z, markers, given)`)
given as the node. Its reliability is from now, `pred.sf(age + t)`; its
lifetimes `pred.random() - age`; its mean the unit's mean life left. It
takes part in the reliability, MTTF, B-lives, importance measures,
states, simulations and saving. Its age is its prediction's: a
`NodeState` with another age is refused, as it would condition the unit
again. A `RepairableRBD` and a `NonRepairable` refuse a joint model,
whose units after the first are new and without its readings. See the
condition-based evaluation guide.

### Changed

- surpyval 0.25 is required (for its joint models).

### Removed

- `NodeState`'s `phase`, deprecated in 0.14 and renamed `since_scheduled`
(#350): giving it raises `TypeError`, and a state has no `phase`.

## [0.14] - 2026-10-16

Fitted models in, every kind of model brought up to the diagram, and
Expand Down
133 changes: 56 additions & 77 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# RePyability — project notes for Claude

Setup, branches, the checks a change needs, where tests go, pull requests,
deprecations and releases are in [CONTRIBUTING.md](CONTRIBUTING.md); read it
first. This file adds the internals and conventions it does not cover.

**Versions (17 Oct 2026):** 0.14 is the latest release (PyPI, `v0.14`). 0.15
is prepared on `dev` (`_version.py`, CHANGELOG dated 2026-10-17) and waits on
the release pull request from `dev` into `main`. `dev` needs surpyval 0.25
or later.

## Architecture & scope

- **Distribution/lifetime fitting stays in surpyval, not RePyability.** surpyval
Expand All @@ -9,8 +18,8 @@
"from data to system reliability" workflow is wanted, do the fitting in
surpyval and pass the resulting models in — do not add fitting logic here.

- **Visualization stays out of RePyability.** The Reliafy app (separate,
open source) handles plotting/dashboards/reporting. RePyability is the
- **Visualization stays out of RePyability.** The Reliafy app (separate)
handles plotting/dashboards/reporting. RePyability is the
computational reliability engine; keep it free of plotting dependencies.

- **RePyability is about timelines; Reliafy is about calendars.**
Expand Down Expand Up @@ -38,12 +47,12 @@

- **surpyval releases reach RePyability users at once.** surpyval is
required with no upper bound, so its next release is what a fresh install
gets. No automated run tests surpyval's `develop` (the maintainer wants
one automated test run, below): before surpyval releases, run
RePyability's tests against its `develop` by hand (`pip install
"surpyval @ git+https://github.com/derrynknife/SurPyval@develop"`), and
gets. No automated run tests surpyval's `dev` (there is one automated
test run, on the release pull request): before surpyval releases, run
RePyability's tests against its `dev` by hand (`pip install
"surpyval @ git+https://github.com/derrynknife/SurPyval@dev"`), and
when they fail, fix RePyability (working with both the released surpyval
and `develop`) and release that first. CI's `test (minimum dependencies)`
and `dev`) and release that first. CI's `test (minimum dependencies)`
job tests the oldest surpyval `pyproject.toml` allows (and every other
dependency at its floor); raise that minimum,
rather than keep code for older versions, once RePyability needs what a
Expand Down Expand Up @@ -423,38 +432,24 @@
that `levers()` lists what `parameter_sensitivity` reports, in its
order, that each value lies in its range, and that `with_levers`
refuses a value past it.
- **A deprecation gives one minor release's notice.** It warns in one
minor release and the next removes it, with a `FutureWarning` (always
shown) through `repyability/utils/deprecation.py`. What 0.11 deprecated
went in 0.12 (#149), and `test_removed_in_0_12.py` keeps it gone: the
old names and ignored arguments raise `TypeError`, a diagram refuses a
non-parametric node, and a standby or load-sharing model with no exact
or numerical reliability refuses one (`is_simulated`), where a fit to
simulated lifetimes stood in, and is left to the simulations. What 0.12
deprecated went in 0.13, and `test_removed_in_0_13.py` keeps it gone:
calling `SparesDemand.mean()`/`std()` (#184) and those models'
`mc_samples`, `lower` and `seed` raise `TypeError`, and `StandbyModel`'s
options after them are given by name. What 0.13 deprecated went in
0.14, and `test_removed_in_0_14.py` keeps it gone:
`optimal_inspection_intervals(offsets=)` (#222) raises `TypeError`, for
`offset_shares=`; `CapacityDistribution.mean` is a plain number or
array, not callable (#235); the exact `mean` of a `StandbyModel`,
`LoadSharingModel` or `DegradingNode` refuses `mc_samples` and `seed`
(#233, `standby_node.drawn_mean`); and `rbd._sensitivity`'s `levers`
and `Lever` are gone (#244). What 0.14 deprecates goes in 0.15
(`NEXT_REMOVAL`): `NodeState`'s `phase`, renamed `since_scheduled`
(#350, through `deprecation.renamed` for the argument and
`renamed_attribute` for reading it); a new deprecation warns through
those, `called` or `ignored`. `test_what_0_14_deprecates_goes_in_0_15`
fails once the version reaches it.
- **Deprecations** follow CONTRIBUTING (one minor release's notice, a
`FutureWarning` through `repyability/utils/deprecation.py`). Each
release's removals stay gone through `test_removed_in_0_12.py`,
`_0_13.py`, `_0_14.py` and `_0_15.py` (`NodeState`'s `phase`, #350):
a removed argument raises `TypeError`. 0.15 deprecates nothing; what it
would deprecate goes in 0.16 (`NEXT_REMOVAL`). A new deprecation warns
through `deprecation.renamed`, `renamed_attribute`, `called` or
`ignored`, with a test that fails once the version reaches its removal
(as `test_what_0_14_deprecates_goes_in_0_15` did).
- **No result depends on the unit of time (or of a covariate).** A step,
grid or probe is sized from the models (a typical life,
`failure_time_scale`), never in absolute time, and a proportional
change of a lever is of its scale (`_sensitivity._proportional_scale`:
1 for a log-scale parameter, none for a covariate). `test_units.py`
checks results scale with the unit, from 1e-6 to 1e6. A model's unit
is surpyval's to carry (SurPyval#845); until it does, every node is
taken to be in one unit, unchecked.
is surpyval's to carry: its next release labels a model with one
(`model.unit`, SurPyval#845, not converted or checked). Until RePyability
reads it, every node is taken to be in one unit, unchecked.
- **Exact by default, simulation on request.** Where an analysis can be
computed exactly or numerically, that is the default, and the Monte-Carlo
estimate is a `method="simulate"` away (as for `NonRepairableRBD.mean`).
Expand All @@ -471,48 +466,24 @@
them (a renewal sum, a band of a grid) then runs hundreds of times
slower. Matrix products are left to BLAS.

## Testing

- **One automated test run: the full suite on the pull request from dev
into master.** `actions.yml` runs on nothing else: not on pushes, not on
pull requests into dev, not on a schedule, and there is no other test
workflow. Work going into dev is checked locally by the tests of what it
changes (the files that exercise the code touched, and new tests for
it), and is merged without waiting for CI. Do not start, re-run or add
other automated test runs; run the full suite locally only when the
maintainer asks.

## Releasing

Releases are cut from master by `.github/workflows/release.yml`, which this
session can run: it cannot push tags or create GitHub Releases itself. Every
merge and release needs the maintainer's go-ahead.

Versions have two parts, major.minor, from 0.11 (they had three until
0.10.1): from 1.0, a release that breaks compatibility raises the major
number, and any other release, fixes included, the minor. There are no
patch releases, and `release.yml` refuses a version that isn't
major.minor.

1. On the working branch, bump `repyability/_version.py`. Roll the CHANGELOG
`[Unreleased]` section into `## [X.Y] - YYYY-MM-DD`, opening with a
summary paragraph and the behaviour changes (they open the release notes),
and update the version in `docs/guide/saving.md`. PR to dev, then dev to
master. A pull request into dev lists "Closes #N" for each issue it
finishes, and `.github/workflows/close-issues.yml` closes them when it
merges, with a comment naming it (GitHub itself closes an issue only on a
merge into master): commit messages' "(#N)" close nothing, and the
dev-to-master pull request need not list them again.
2. When the full suite has passed on the pull request from dev into
master, merge it (a merge commit) and run the workflow with
`actions_run_trigger`: `run_workflow`, workflow `release.yml`, ref
`master`, inputs `{"version": "X.Y", "dry_run": "true"}`. If that
passes, run it again with `"dry_run": "false"`. Then check the run, the
tag, the GitHub Release and `https://pypi.org/pypi/repyability/X.Y/json`.
## Testing and releasing

The checks, the one automated test run and the release steps are in
CONTRIBUTING.md. What it leaves to the maintainer's say:

- Do not start, re-run or add automated test runs, and run the full suite
locally only when the maintainer asks. Work going into `dev` is checked
locally by the tests of what it changes (`scripts/check.sh <tests>`) and
merged without waiting for CI.
- Every merge and every release needs the maintainer's go-ahead. Merge the
release pull request with a merge commit (`release.yml` finds CI's pass
on the merged `dev` commit), run `release.yml` with `dry_run` first, and afterwards check the run, the tag
`vX.Y`, the GitHub Release and `https://pypi.org/pypi/repyability/X.Y/json`.

## surpyval workarounds to remove

surpyval 0.24 (the minimum) gives a mixture a precise tail (`sf`, #671) and
surpyval 0.25 (the minimum) has the joint models a `JointNode` takes; 0.24
gives a mixture a precise tail (`sf`, #671) and
has `conditional_gaps` read a limited failure population's `lfp_p`, not a
mixture's `p` (#626); 0.23 pickles its fits (#573), names the
limited-failure proportion `lfp_p` (#608) and takes `success_run`'s
Expand All @@ -521,8 +492,8 @@ limited-failure proportion `lfp_p` (#608) and takes `success_run`'s
- **A mixture's quantile** (surpyval #821). surpyval 0.24's `MixtureModel`
has a `qf` (#651), but it inverts `F` in the upper tail, losing the long
lives' precision (about 1e-4), and takes some 200 times as long as
RePyability's. `_sampling.mixture_quantile` inverts the distribution
(with its components' `sf` summed above the median), which
RePyability's (fixed on surpyval's `dev`, for its next release).
`_sampling.mixture_quantile` inverts the distribution (with its components' `sf` summed above the median), which
`stream_sampler` gives a `RepairableRBD`'s streams (a change to it
changes seeded results). Take the mixture's own `qf` once the minimum
surpyval's is as precise and fast.
Expand All @@ -544,11 +515,19 @@ limited-failure proportion `lfp_p` (#608) and takes `success_run`'s
change to it changes seeded results). Take surpyval's once the minimum
surpyval has it.

- **Rescaling a model's time for a duty cycle** (surpyval #845).
A spec's `"duty"` puts a life in operating time on the calendar,
`R(d t)`, by `_duty._SCALED`, one rule per surpyval distribution (a new
- **Rescaling a model's time for a duty cycle.** surpyval has no
rescaling (#845 gave models only a unit label). A spec's `"duty"` puts a
life in operating time on the calendar, `R(d t)`, by `_duty._SCALED`, one rule per surpyval distribution (a new
one needs its rule there, which `test_units` checks against `R(d t)`).
Take surpyval's rescaling once the minimum surpyval has it.
- **A fit's offset under parameter uncertainty** (surpyval #830).
surpyval's `covariance()` leaves an offset fit's `gamma` out, so
`rbd/uncertainty.py` holds it where it was fitted, with a warning. Draw it
once the minimum surpyval's covariance includes it.

Refused until surpyval has it: an ARA, ARI or G1 renewal model as a life,
whose next failure surpyval cannot draw yet (surpyval #833,
`rbd/_processes.py`).

List each new workaround here with its surpyval issue and where it lives,
so it can go once the minimum surpyval in `pyproject.toml` includes the
Expand Down
18 changes: 10 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ pip install -e .[dev] # add ,fast for numba, ,docs for mkdocs
pre-commit install # run the formatters and linters on commit
```

RePyability needs Python 3.11 or later and surpyval 0.24 or later.
RePyability needs Python 3.11 or later and surpyval 0.25 or later.

## Branches and the one automated test run

Work branches off **`dev`** and goes back into `dev` by pull request.
Releases go from `dev` into `master`.
Releases go from `dev` into `main`.

There is **one automated test run**: the full suite on the pull request from
`dev` into `master` (`.github/workflows/actions.yml`). Nothing runs on pushes
`dev` into `main` (`.github/workflows/actions.yml`). Nothing runs on pushes
or on pull requests into `dev`, so check your change locally before it goes
in:

Expand Down Expand Up @@ -104,8 +104,10 @@ go once the minimum surpyval version has the fix.

A deprecation warns (a `FutureWarning`, through
`repyability/utils/deprecation.py`) for one minor release, and the next
removes it. `test_what_0_14_deprecates_goes_in_0_15` and its kind fail once
the version reaches the removal, as a reminder.
removes it. A test of the kind `test_what_0_14_deprecates_goes_in_0_15`
(in `test_api_consistency.py`) fails once the version reaches the removal,
as a reminder; the removed names then stay gone through
`test_removed_in_X_Y.py`.

## Releases (maintainers)

Expand All @@ -118,10 +120,10 @@ release:
`[Unreleased]` section into a dated `## [X.Y] - YYYY-MM-DD` section. Its
opening paragraphs become the summary at the top of the release notes.
Update the version in `docs/guide/saving.md`. Merge to `dev`, then open
the pull request from `dev` into `master`, which runs the full suite.
2. Once it has passed, merge it and run the `release` workflow on master
the pull request from `dev` into `main`, which runs the full suite.
2. Once it has passed, merge it and run the `release` workflow on main
with the version: Actions → release → Run workflow, or
`gh workflow run release.yml --ref master -f version=X.Y`. Add
`gh workflow run release.yml --ref main -f version=X.Y`. Add
`-f dry_run=true` to check and build without publishing. The workflow
checks the version, the CHANGELOG section, that the tag is new, that CI
passed and that PyPI does not have the version. Then it builds, publishes
Expand Down
Loading
Loading