Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
e4586fe
CI: one automated test run, the full suite on the PR from dev into ma…
derrynknife Oct 9, 2026
272c5a6
CI: run the suite on every core; measure coverage on Python 3.14 alon…
derrynknife Oct 9, 2026
b007000
Require surpyval 0.24 (#264)
derrynknife Oct 9, 2026
56810ed
RegressionNode: exact mean and draws from surpyval 0.24's regression …
derrynknife Oct 9, 2026
e3b0837
Uncertainty draws lfp_p/f0; precise conditional survival; fitted repa…
derrynknife Oct 9, 2026
661d5fb
Start an imperfectly repaired unit from its virtual age (#274)
derrynknife Oct 9, 2026
30d5145
A regression node's covariates are levers (#275)
derrynknife Oct 10, 2026
28eafb6
Degradation processes as lives, replacement on condition by level (#2…
derrynknife Oct 10, 2026
873deee
Review, first findings: lint passes, stale docs fixed, contributor gu…
derrynknife Oct 10, 2026
4fa2de7
Review, second findings: dead code, narrower catches, shared checks, …
derrynknife Oct 10, 2026
d9f13e3
Review, docs findings: API pages split, issue numbers out of docstrin…
derrynknife Oct 10, 2026
d915c8a
A component whose life never fails is a junction, a probability of 0 …
derrynknife Oct 10, 2026
c0de000
Split repairable_rbd.py, step 1: the event records, the tally, the ti…
derrynknife Oct 10, 2026
4d6d5eb
Split repairable_rbd.py, step 2: the spec checks, as functions of _sp…
derrynknife Oct 10, 2026
c3b788b
Split repairable_rbd.py, step 3: the shared helpers, in _common (#283)
derrynknife Oct 10, 2026
596c47b
Split repairable_rbd.py, step 4: spares, as functions of _spares (#284)
derrynknife Oct 10, 2026
ddc6915
Split repairable_rbd.py, step 5: the allocations, in _repairable_allo…
derrynknife Oct 10, 2026
9e200a8
Split repairable_rbd.py, step 6: the maintenance intervals, in _inter…
derrynknife Oct 10, 2026
6d91a67
Split repairable_rbd.py, step 7: the long-run costs, in _costs (#287)
derrynknife Oct 10, 2026
b68485b
Split repairable_rbd.py, step 8: the capacity, in _repairable_capacit…
derrynknife Oct 10, 2026
b6d4de1
Split repairable_rbd.py, step 9: the common-cause groups, in _ccf_gro…
derrynknife Oct 10, 2026
a95729e
Split repairable_rbd.py, step 10: the importance measures, in _repair…
derrynknife Oct 10, 2026
a7781b3
Split repairable_rbd.py, step 11: the long-run values, in _long_run (…
derrynknife Oct 10, 2026
c7c2a75
Split repairable_rbd.py, step 12: values over time from the curves, i…
derrynknife Oct 10, 2026
4eb8df7
Split repairable_rbd.py, step 13: the components' curves, in _curves …
derrynknife Oct 10, 2026
8f1c790
Split repairable_rbd.py, step 14: the crews' chain and the checks, in…
derrynknife Oct 10, 2026
0aabfa2
Split repairable_rbd.py, step 15: uncertainty and sensitivity, into t…
derrynknife Oct 10, 2026
ae9bfc0
Split repairable_rbd.py, step 16: the runs, in _runs (#299)
derrynknife Oct 10, 2026
01ca09f
Split repairable_rbd.py, step 17: the event loop, in _event_loop (#300)
derrynknife Oct 10, 2026
48be271
Split repairable_rbd.py, step 18: analysis_routes in _repairable_rout…
derrynknife Oct 10, 2026
8f4a702
A simulation's state in an object of its own (_events._Run) (#302)
derrynknife Oct 10, 2026
f827666
A diagram's kept results in one declared object (rbd._Cache) (#303)
derrynknife Oct 10, 2026
a57d037
Concepts is the reference, Learn the course: concepts.md stops re-tea…
derrynknife Oct 10, 2026
8d4b205
CHANGELOG: the split of RepairableRBD's code, the run's state, and th…
derrynknife Oct 10, 2026
fd7b73a
Importance measures with common-cause groups whose tests take time ar…
derrynknife Oct 10, 2026
6e293d9
The failure frequency, MUT and MDT with common-cause groups whose tes…
derrynknife Oct 10, 2026
d1b7b9b
A mixture life with imperfect repair simulates in seconds (#308)
derrynknife Oct 10, 2026
198ac52
CLAUDE.md: the mixture's life-left workaround waits on surpyval #857 …
derrynknife Oct 10, 2026
298f7d3
Add capability matrices to the README (#310)
derrynknife Oct 11, 2026
fecd1d4
Start the maintenance forecaster's use-case record (#323)
derrynknife Oct 11, 2026
fcb5162
Calendar tasks and their due dates (#319, UC-2) (#324)
derrynknife Oct 11, 2026
a12f25f
Give calendar tasks a cost each time (#319, UC-3) (#325)
derrynknife Oct 11, 2026
b0a8bc8
Monthly rules for calendar tasks, on python-dateutil (#319, UC-4) (#326)
derrynknife Oct 11, 2026
d4707e9
Record that public holidays are set aside for now (#327)
derrynknife Oct 11, 2026
2fe2968
Forecast the failures of a component replaced when it fails (#319, UC…
derrynknife Oct 11, 2026
7374984
The NPV of skipping the next planned replacement (#319, UC-6) (#329)
derrynknife Oct 11, 2026
4301f45
Tasks by use on an asset's operating schedule (#319, UC-10); UC-7 to …
derrynknife Oct 12, 2026
0c9a2de
Tasks by use on an asset's operating schedule (#319, UC-10); UC-7 to …
derrynknife Oct 12, 2026
181d9f6
A rule naming days goes in Task(on=...), its day by name (#319) (#332)
derrynknife Oct 12, 2026
c28fe38
Scope: RePyability is about timelines, Reliafy about calendars (#334)
derrynknife Oct 12, 2026
c7d9a29
Calendars to Reliafy; a part's failures in model time on NonRepairabl…
derrynknife Oct 12, 2026
1b66fff
UC-5 asked in model time (#319) (#336)
derrynknife Oct 12, 2026
a2636d2
The maintenance use cases, redone in model time (#319) (#337)
derrynknife Oct 13, 2026
7ffc17a
Record UC-11: a part renewed at every service and at failure (#319) (…
derrynknife Oct 13, 2026
258ea1b
The best replacement policy when a shut is the cheapest time to repla…
derrynknife Oct 13, 2026
73a17ac
When to replace a part in service next: NonRepairable.optimal_next_re…
derrynknife Oct 16, 2026
5f827c6
optimal_next_replacement: scheduled outages only, valued at the repla…
derrynknife Oct 16, 2026
649e2e6
The long-run optimal replacement policy over a calendar of shuts (#31…
derrynknife Oct 16, 2026
aae4e0e
CalendarPolicy says what it does: rates and costs by kind, next_repla…
derrynknife Oct 16, 2026
3995141
The calendar policy takes no step; replacement_policies goes (#319, U…
derrynknife Oct 16, 2026
545528b
optimal_next_replacement takes a policy as its baseline (#348, UC-12)
claude Oct 16, 2026
22885cf
Merge wip/348: optimal_next_replacement takes a policy as its baselin…
claude Oct 16, 2026
8768e40
Merge remote-tracking branch 'origin/claude/zealous-lovelace-49rthw' …
claude Oct 16, 2026
8ca104e
Criticality and Fussell-Vesely over time take a nested RBD in with re…
claude Oct 16, 2026
870b834
Merge wip/312: criticality and Fussell-Vesely over time with crews ar…
claude Oct 16, 2026
f65e213
UC-6 as NonRepairable.skip_next_replacement; NodeState.phase is since…
claude Oct 16, 2026
6ef1a95
Merge wip/350: skip_next_replacement on NonRepairable; NodeState.sinc…
claude Oct 16, 2026
110e2cd
Alpha-factor common-cause model (#311)
claude Oct 16, 2026
94e9b52
Merge wip/311: alpha-factor common-cause model (#311)
claude Oct 16, 2026
9ebf8da
spares_stock for a block-replaced unit dead on arrival whose renewals…
claude Oct 16, 2026
0a0c065
Merge wip/322: spares_stock for a block-replaced unit dead on arrival…
claude Oct 16, 2026
a3c526d
Joint importance with common-cause groups (#317)
Oct 16, 2026
e9a29cd
Merge wip/317: joint importance with common-cause groups (#317)
claude Oct 16, 2026
3d8cc9d
Minimal repair whose repairs take time, over a window (#318)
claude Oct 16, 2026
b76ef4b
A calendar component in a maintenance group is a member like any othe…
claude Oct 16, 2026
9b31836
Merge wip/349: a calendar component in a maintenance group (#349)
claude Oct 16, 2026
9cf7770
Phased mission importance measures and cut sets (#316)
claude Oct 16, 2026
b024aef
Merge wip/316: phased mission importance measures and cut sets (#316)
claude Oct 16, 2026
3696eab
Merge wip/318: minimal repair whose repairs take time, over a window …
claude Oct 16, 2026
657f0c5
The calendar-policy example in the maintenance guide defines its part…
claude Oct 16, 2026
9405602
A network's importance measures, B-lives, uncertainty, saving and rou…
claude Oct 16, 2026
5bc2ee6
Merge wip/315: a network's importance measures, B-lives, uncertainty,…
claude Oct 16, 2026
8417396
FaultTree: MTTF, B-life, density and hazard, the remaining importance…
Oct 16, 2026
d4e3329
Merge wip/314: fault tree MTTF, B-lives, remaining measures, sensitiv…
claude Oct 16, 2026
b0f23d6
CLAUDE.md: the shared measures module, and the routes tests of every …
claude Oct 16, 2026
1b52d75
The calendar decisions file ships as package data; a tree's joint imp…
claude Oct 16, 2026
dde82ef
The maintenance planner's review findings: a shut off the grid keeps …
claude Oct 16, 2026
d60a10d
Merge wip/review-c: the maintenance planner's review findings
claude Oct 16, 2026
39f2ced
Fix the 0.14 persona review's findings on trees, networks, missions a…
claude Oct 16, 2026
a095d1e
Common-cause groups: tests that take time over a window, members' own…
claude Oct 16, 2026
cc9b5db
Merge wip/review-a: the persona review's findings on trees, networks,…
claude Oct 16, 2026
1a8fa96
Merge wip/review-b: common-cause groups over a window, members' own o…
claude Oct 16, 2026
d1aaacc
Repairable systems: the persona review's fixes
claude Oct 16, 2026
f055af6
Merge wip/review-d: repairable systems, the persona review's fixes
claude Oct 16, 2026
9d42337
The input-check diagram is priced, and AnalysisRoutes is no node
claude Oct 16, 2026
494d9f2
The small and medium issues, and the 0.14 persona review's fixes (#351)
derrynknife Oct 16, 2026
dff1db9
Close the issues a pull request into dev finishes, when it merges
claude Oct 16, 2026
cf0a63e
Close the issues a pull request into dev finishes, when it merges (#352)
derrynknife Oct 16, 2026
11b88cd
Release 0.14: remove what 0.13 deprecated; version, CHANGELOG, saving…
derrynknife Oct 16, 2026
ef88185
Docs: two links the strict build refuses (#354)
derrynknife Oct 16, 2026
d3950f0
Narrow a minimal-repair curve's grid values for mypy 2.3.0 (#355)
derrynknife Oct 16, 2026
019cccd
The crews' system has the jump_times hook; two tests follow dev's cha…
derrynknife Oct 16, 2026
25d3fc5
Roadmap and user stories (as of 0.13) (#342)
derrynknife Oct 16, 2026
f4fe4cd
CHANGELOG: the roadmap and user stories (#342) (#357)
derrynknife Oct 16, 2026
f4a6053
A fixed duration is fixed whatever numpy's rounding; an always-up mis…
derrynknife Oct 16, 2026
c7528bb
A fixed duration is its atom's time; integrated costs compared to rou…
derrynknife Oct 16, 2026
8160dc8
A system held perfect is compared to rounding (#361)
derrynknife Oct 16, 2026
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/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
## Checklist

- [ ] Tests added/updated (asserting against a reference value where possible)
- [ ] `black`, `isort`, `flake8`, and `mypy` pass
- [ ] `coverage run -m pytest` passes and stays above the coverage gate
- [ ] `scripts/check.sh` passes (black, isort, flake8, mypy), with the tests of what changed
- [ ] Anything that must agree with the change is updated with it (see CONTRIBUTING.md, "What must agree")
- [ ] `CHANGELOG.md` updated under `[Unreleased]`
- [ ] Any Monte-Carlo tests are seeded (deterministic)

Expand Down
69 changes: 39 additions & 30 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ name: actions

run-name: RePyability CI

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

jobs:
lint:
name: lint (black, isort, flake8, mypy)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v5

Expand Down Expand Up @@ -36,6 +42,7 @@ jobs:
test:
name: test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
Expand All @@ -53,25 +60,32 @@ jobs:
python -m pip install --upgrade pip
pip install -e .[dev]

- name: pytest
# Every version runs the suite on all the runner's cores; coverage is
# measured once, on the newest Python (next step).
if: matrix.python-version != '3.14'
run: python -m pytest -n auto -q --durations=30

- name: pytest (under coverage)
# From Python 3.12 coverage can measure through sys.monitoring,
# which counts the same lines as its default tracer (sys.settrace)
# several times faster: under the tracer, Python 3.12's job took
# over an hour. Python 3.11 has no sys.monitoring, and an empty
# COVERAGE_CORE leaves it the default tracer.
# Coverage is measured on Python 3.14 alone, through sys.monitoring,
# which is several times faster than its default tracer
# (sys.settrace): under the tracer, Python 3.11's job took an hour.
# pytest-cov collects it from each of pytest-xdist's workers.
if: matrix.python-version == '3.14'
env:
COVERAGE_CORE: ${{ matrix.python-version != '3.11' && 'sysmon' || '' }}
run: coverage run -m pytest
COVERAGE_CORE: sysmon
run: python -m pytest -n auto -q --durations=30 --cov --cov-report=

- name: coverage report (enforces the fail_under gate)
if: matrix.python-version == '3.14'
run: coverage report

- name: coverage html
if: matrix.python-version == '3.11'
if: matrix.python-version == '3.14'
run: coverage html

- name: Upload coverage html report artifact
if: matrix.python-version == '3.11'
if: matrix.python-version == '3.14'
uses: actions/upload-artifact@v4
with:
name: coverage-html-report
Expand All @@ -83,6 +97,7 @@ jobs:
# the compiled engine runs and its results are checked against Python's.
name: test (with numba, Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
Expand All @@ -102,13 +117,16 @@ jobs:
python -c "import numba; print('numba', numba.__version__)"

- name: pytest
run: python -m pytest -q
run: python -m pytest -n auto -q --durations=30

test-minimum:
# CI otherwise installs the newest surpyval, so once surpyval releases,
# nothing would test the oldest one pyproject.toml still allows.
name: test (minimum surpyval)
# The other jobs install the newest of every dependency, so nothing
# else tests the oldest that pyproject.toml allows: this one installs
# each dependency at its floor (numpy, scipy, networkx, surpyval, tqdm),
# on the oldest Python, then the test tools.
name: test (minimum dependencies)
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@v5

Expand All @@ -117,29 +135,20 @@ jobs:
with:
python-version: '3.11'

- name: Install the package, then the oldest surpyval it allows
- name: Install the package at its dependencies' floors
run: |
python -m pip install --upgrade pip
pip install -e .[dev]
minimum=$(python - <<'EOF'
import re
import tomllib

with open("pyproject.toml", "rb") as f:
dependencies = tomllib.load(f)["project"]["dependencies"]
spec = next(d for d in dependencies if d.startswith("surpyval"))
print(re.search(r">=\s*([0-9][0-9.]*)", spec).group(1))
EOF
)
pip install "surpyval==$minimum"
python -c "import surpyval; print('surpyval', surpyval.__version__)"
python -m pip install --upgrade pip uv
uv pip install --system --resolution lowest-direct -e .
uv pip install --system "pytest>=7.0" "pytest-xdist>=3.5" "pytest-timeout>=2.3"
python -c "import numpy, scipy, networkx, surpyval, tqdm; print('numpy', numpy.__version__, 'scipy', scipy.__version__, 'networkx', networkx.__version__, 'surpyval', surpyval.__version__, 'tqdm', tqdm.__version__)"

- name: pytest
run: python -m pytest -q
run: python -m pytest -n auto -q --durations=30

docs:
name: docs (strict build)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v5

Expand Down
56 changes: 56 additions & 0 deletions .github/workflows/close-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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
# 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).

on:
pull_request:
types: [closed]
branches: [dev]

permissions:
issues: write
pull-requests: read

jobs:
close:
name: close the issues the pull request finishes
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7
with:
script: |
const pull = context.payload.pull_request;
const body = pull.body || "";
// The keywords GitHub takes, in a body line such as "Closes #N".
const pattern = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi;
const numbers = new Set();
for (const match of body.matchAll(pattern)) {
numbers.add(Number(match[1]));
}
for (const issue_number of numbers) {
const { data: issue } = await github.rest.issues.get({
...context.repo,
issue_number,
});
if (issue.pull_request || issue.state === "closed") {
continue;
}
await github.rest.issues.createComment({
...context.repo,
issue_number,
body: `Merged into dev by #${pull.number}: it reaches users with the next release.`,
});
await github.rest.issues.update({
...context.repo,
issue_number,
state: "closed",
state_reason: "completed",
});
core.info(`Closed #${issue_number}`);
}
24 changes: 20 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ name: release
# `gh workflow run release.yml --ref master -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 CI passed on the commit and
# 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
# 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 @@ -87,11 +88,26 @@ jobs:
echo "::error::The tag v$version already exists."
exit 1
fi
passed=$(gh api "repos/$GITHUB_REPOSITORY/actions/workflows/actions.yml/runs?head_sha=$GITHUB_SHA&status=success" --jq '.total_count')
if [ "$passed" -eq 0 ]; then
echo "::error::CI (actions.yml) has not passed on $GITHUB_SHA; wait for it, or fix it."
# The full suite runs on the pull request from dev into master
# (actions.yml), whose run is recorded against dev's commit. The
# merge commit on master 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')
passed=""
for sha in "$GITHUB_SHA" $(gh api "$repo/git/commits/$GITHUB_SHA" --jq '.parents[].sha'); do
[ "$(gh api "$repo/git/commits/$sha" --jq '.tree.sha')" = "$tree" ] || continue
runs=$(gh api "$repo/actions/workflows/actions.yml/runs?head_sha=$sha&status=success" --jq '.total_count')
if [ "$runs" -gt 0 ]; then
passed=$sha
break
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."
exit 1
fi
echo "The full suite passed on $passed, with the same files as $GITHUB_SHA."
pypi=$(curl -s -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/repyability/$version/json")
if [ "$pypi" = 200 ]; then
echo "::error::repyability $version is already on PyPI."
Expand Down
46 changes: 0 additions & 46 deletions .github/workflows/upstream.yml

This file was deleted.

20 changes: 7 additions & 13 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,10 @@
repos:
# Organise imports
- repo: https://github.com/PyCQA/isort
rev: "6.0.1"
rev: "9.0.1"
hooks:
- id: isort

# Update old python syntax
- repo: https://github.com/asottile/pyupgrade
rev: "v3.19.1"
hooks:
- id: pyupgrade
args: ["--py310-plus"]

# Format with Black
- repo: https://github.com/psf/black
rev: "26.3.1"
Expand All @@ -27,18 +20,19 @@ repos:

# Lint with flake8 (reads [tool.flake8] from pyproject via flake8-pyproject)
- repo: https://github.com/PyCQA/flake8
rev: "7.3.0"
rev: "7.4.1"
hooks:
- id: flake8
additional_dependencies: ["flake8-pyproject"]

# Uses the locally installed mypy (the mirror uses opinionated args by
# default); tests are excluded to match the CI configuration.
# default), on the whole package as CI runs it: checking the staged files
# alone misses errors their changes cause elsewhere.
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
entry: mypy repyability
language: system
types: [python]
exclude: "^repyability/tests/"
pass_filenames: false
files: "^repyability/"
Loading
Loading