From 00951503d81ebef0bd45b28f0ddca96693db2147 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Thu, 14 May 2026 00:20:55 -0700 Subject: [PATCH 1/3] Add workflow to sync main into docs/great-docs-prototype On every push to main, GitHub Actions automatically merges main into docs/great-docs-prototype so the docs branch stays current with all code changes while keeping its doc-engine files intact. --- .github/workflows/sync-main-to-docs.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/sync-main-to-docs.yml diff --git a/.github/workflows/sync-main-to-docs.yml b/.github/workflows/sync-main-to-docs.yml new file mode 100644 index 00000000..ae11e38f --- /dev/null +++ b/.github/workflows/sync-main-to-docs.yml @@ -0,0 +1,25 @@ +name: Sync main to docs/great-docs-prototype + +on: + push: + branches: [main] + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Merge main into docs/great-docs-prototype + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout docs/great-docs-prototype + git merge origin/main --no-edit -m "chore: sync main into docs/great-docs-prototype" + git push origin docs/great-docs-prototype From 992622e9d217d9ccc4d17135eeac99c4da94aa00 Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Sat, 13 Jun 2026 20:57:47 -0700 Subject: [PATCH 2/3] Document creating triangles with preexisting ultimates Add Triangle constructor examples and expand _split_ult docstring for round-trip and direct import via options.ULT_VAL. Add test_create_triangle_with_ultimates. Closes #523. Co-authored-by: Cursor --- chainladder/core/tests/test_triangle.py | 31 +++++++++++ chainladder/core/triangle.py | 71 ++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/chainladder/core/tests/test_triangle.py b/chainladder/core/tests/test_triangle.py index dc1a7c4c..b8c0a1be 100644 --- a/chainladder/core/tests/test_triangle.py +++ b/chainladder/core/tests/test_triangle.py @@ -403,6 +403,37 @@ def test_create_full_triangle(raa): assert a == b +def test_create_triangle_with_ultimates(raa): + """Round-trip and direct import of triangles with ultimate values.""" + ult = cl.Chainladder().fit(raa).ultimate_ + round_tripped = cl.Triangle( + ult.to_frame(keepdims=True, origin_as_datetime=True), + origin="origin", + development="valuation", + columns="values", + cumulative=True, + ) + assert round_tripped.is_ultimate + assert round_tripped == ult + + direct = cl.Triangle( + pd.DataFrame( + { + "origin": pd.to_datetime(["1981-01-01", "1982-01-01"]), + "valuation": pd.to_datetime( + [cl.options.ULT_VAL, cl.options.ULT_VAL] + ), + "paid": [10000.0, 12000.0], + } + ), + origin="origin", + development="valuation", + columns="paid", + cumulative=True, + ) + assert direct.is_ultimate + + def test_groupby_getitem(clrd): assert ( clrd.groupby("LOB")["CumPaidLoss"].sum() diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index a6a33b52..e6e23b18 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -335,6 +335,62 @@ class Triangle(TriangleBase): 2024Q2 130.0 2024Q3 160.0 2024Q4 140.0 + + Triangles with ultimate values + ------------------------------ + + Triangles produced by reserving methods carry ultimate projections at the + sentinel valuation date ``options.ULT_VAL`` (default December 31, 2261). + Export with ``to_frame(keepdims=True)`` and reconstruct by passing + ``development='valuation'``. Rows whose valuation equals + ``options.ULT_VAL`` are recognized as ultimates and stored in the ultimate + development column (see also :attr:`is_ultimate`). + + .. testcode:: + + raa = cl.load_sample('raa') + ult = cl.Chainladder().fit(raa).ultimate_ + df = ult.to_frame(keepdims=True, origin_as_datetime=True) + tri = cl.Triangle( + df, + origin='origin', + development='valuation', + columns='values', + cumulative=True, + ) + print(tri.is_ultimate) + + .. testoutput:: + + True + + Pre-existing ultimate estimates can be supplied directly in long-format + data by setting the valuation column to ``options.ULT_VAL`` for each + origin. + + .. testcode:: + + df = pd.DataFrame( + data={ + 'origin': pd.to_datetime(['1981-01-01', '1982-01-01']), + 'valuation': pd.to_datetime([ + cl.options.ULT_VAL, cl.options.ULT_VAL + ]), + 'paid': [10000.0, 12000.0], + } + ) + tri = cl.Triangle( + df, + origin='origin', + development='valuation', + columns='paid', + cumulative=True, + ) + print(tri.is_ultimate) + + .. testoutput:: + + True """ def __init__( @@ -572,7 +628,20 @@ def _split_ult( origin: list, development: list ) -> tuple[DataFrame, Triangle]: - """Deal with triangles with ultimate values.""" + """Split ultimate valuation rows from long-format triangle data. + + Ultimate rows are those where the development column equals + ``options.ULT_VAL``. This supports round-tripping triangles exported + via :meth:`~chainladder.Triangle.to_frame` with ``keepdims=True`` and + ``development='valuation'``. It also allows importing pre-existing + ultimate estimates by marking the valuation column with + ``options.ULT_VAL``. + + Requires a single datetime development column. When ultimate rows are + present (and not every row is an ultimate), they are extracted and + merged back as the ultimate development column after the base triangle + is constructed. + """ ult = None if ( development From e3f641d5752b8d8e6b8f062bdb0fe776e20778fd Mon Sep 17 00:00:00 2001 From: Ethan Kang Date: Thu, 9 Jul 2026 14:43:02 -0700 Subject: [PATCH 3/3] Address review feedback on ultimate triangle docs. Remove the accidental main-to-docs sync workflow from the PR, and print the reconstructed ultimate triangles in both docstring examples. Co-authored-by: Cursor --- .github/workflows/sync-main-to-docs.yml | 25 ------------------------- chainladder/core/triangle.py | 20 ++++++++++++++++---- 2 files changed, 16 insertions(+), 29 deletions(-) delete mode 100644 .github/workflows/sync-main-to-docs.yml diff --git a/.github/workflows/sync-main-to-docs.yml b/.github/workflows/sync-main-to-docs.yml deleted file mode 100644 index ae11e38f..00000000 --- a/.github/workflows/sync-main-to-docs.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Sync main to docs/great-docs-prototype - -on: - push: - branches: [main] - -permissions: - contents: write - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Merge main into docs/great-docs-prototype - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git checkout docs/great-docs-prototype - git merge origin/main --no-edit -m "chore: sync main into docs/great-docs-prototype" - git push origin docs/great-docs-prototype diff --git a/chainladder/core/triangle.py b/chainladder/core/triangle.py index e6e23b18..97cac9c0 100644 --- a/chainladder/core/triangle.py +++ b/chainladder/core/triangle.py @@ -358,11 +358,21 @@ class Triangle(TriangleBase): columns='values', cumulative=True, ) - print(tri.is_ultimate) + print(tri) .. testoutput:: - True + 2261 + 1981 18834.000000 + 1982 16857.953917 + 1983 24083.370924 + 1984 28703.142163 + 1985 28926.736343 + 1986 19501.103184 + 1987 17749.302590 + 1988 24019.192510 + 1989 16044.984101 + 1990 18402.442529 Pre-existing ultimate estimates can be supplied directly in long-format data by setting the valuation column to ``options.ULT_VAL`` for each @@ -386,11 +396,13 @@ class Triangle(TriangleBase): columns='paid', cumulative=True, ) - print(tri.is_ultimate) + print(tri) .. testoutput:: - True + 2261 + 1981 10000.0 + 1982 12000.0 """ def __init__(