Skip to content

Fixes valuation grain bug - #1194

Merged
priyam0k merged 12 commits into
mainfrom
#1192_1D_tri_valuation_date
Aug 10, 2026
Merged

Fixes valuation grain bug#1194
priyam0k merged 12 commits into
mainfrom
#1192_1D_tri_valuation_date

Conversation

@kennethshsu

@kennethshsu kennethshsu commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary of Changes

If valuation_date is of length 1 (there's only 1 diagonal), and it contains no month info, it will be defaulted to match origin grain instead

Related GitHub Issue(s)

Fixes #1192

Additional Context for Reviewers

Should be pretty easy to review, added 3 new tests.

Checklist

  • I passed tests locally for both code (uv run pytest) and documentation changes (uv run --directory docs jb build . --builder=custom --custom-builder=doctest)

Note

Medium Risk
Changes core triangle construction and val/dev conversion logic used across the library; behavior shifts for single-diagonal inputs but is covered by new regression tests.

Overview
Fixes valuation / development grain when every row shares one development date (fixes #1192). Triangles with a single diagonal no longer default to monthly grain when development values are year-only (e.g. 2008 or %Y).

Triangle.__init__ now treats development as year-only when all development column values match \d{4}, or when there is only one data row—then development_grain matches origin_grain. If month-level information exists, it still compares the parsed date to the origin period end before choosing monthly vs origin grain.

val_to_dev / _val_dev always recomputes development-column width from coordinate extents when the array has density, not only when negative development indices need sliding—so single-diagonal triangles keep the correct lag dimension.

The friedland_gl_self_insurer sample manifest drops development_format: "%Y-12-31 so loading uses the same inference rules. New tests cover annual vs monthly single-diagonal cases and the friedland sample’s 2008-12-31 valuation date.

Reviewed by Cursor Bugbot for commit bac78c5. Bugbot is set up for automated code reviews on this repo. Configure here.

@kennethshsu kennethshsu changed the title #1192 1 d tri valuation date Fixes valuation grain bug Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Pyright Type Completeness

View the full pyright --verifytypes output for this commit

Project (full chainladder package, at this PR's head): 15.3% of exported symbols fully typed (200 / 1309)

Known Ambiguous Unknown Total
Project (head) 200 110 999 1309

Other symbols referenced but not exported by chainladder: 13

Known Ambiguous Unknown Total
Other (head) 3 1 9 13

Symbols without documentation:

  • Functions without docstring: 319
  • Functions without default param: 0
  • Classes without docstring: 10

Patch (exported symbols added or changed by this PR): 100.0% fully typed (4 / 4)

Known Ambiguous Unknown Total
Patch 4 0 0 4
Patch symbol details
Symbol Status Change
chainladder.core.tests.test_triangle.test_1d_annual_valuation_date ✅ known new
chainladder.core.tests.test_triangle.test_1d_monthly_valuation_date ✅ known new
chainladder.core.tests.test_triangle.test_1d_monthly_valuation_date_expanded_dev_date ✅ known new
chainladder.core.tests.test_triangle.test_friedland_gl_self_insurer_grain ✅ known new

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.14%. Comparing base (8461cfb) to head (bac78c5).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1194   +/-   ##
=======================================
  Coverage   91.14%   91.14%           
=======================================
  Files          91       91           
  Lines        5365     5367    +2     
  Branches      681      682    +1     
=======================================
+ Hits         4890     4892    +2     
  Misses        340      340           
  Partials      135      135           
Flag Coverage Δ
unittests 91.14% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread chainladder/core/triangle.py
@kennethshsu
kennethshsu marked this pull request as draft August 4, 2026 23:22
@kennethshsu
kennethshsu marked this pull request as ready for review August 4, 2026 23:45
Comment thread chainladder/core/triangle.py Outdated
# checks if development has any non-yearly values
dev_has_no_month = not bool(
development
and not data[development[0]].astype(str).str.fullmatch(r"\d{4}").all()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if pandas parses the development column as floats or with missing values (e.g. 2008.0), .astype(str) yields "2008.0".

that will fail fullmatch(r"\d{4}") and default back to 'M'. stripping trailing .0 or converting to int first would make it a bit safer.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't follow you here, you are saying if the data in the column actually says 2008.0? This is not a valid date string format?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah agree it's not valid date formatting!

it's just a pandas edge case where integer year columns with NaNs get cast to float64 (2008.0). so .astype(str) gives "2008.0", failing r"\d{4}" and defaulting grain to 'M'. super minor edge case though!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give me an actual example? Either with a constructed pandas DF or CSV? I'm having trouble seeing this

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a concrete one. Same annual data as my test, but the dev column comes in as float64 (which is what you often get from a CSV/Excel read, or any time that column lands as float64):

import pandas as pd, chainladder as cl
df = pd.DataFrame({
    "origin": [1998, 1999, 2000, 2001, 2002],
    "dev":    [2008.0, 2008.0, 2008.0, 2008.0, 2008.0],  # float64
    "expense":[890000, 1170000, 1265000, 1600000, 1200000],
})
tri = cl.Triangle(df, origin="origin", development="dev",
                  columns="expense", development_format="%Y", cumulative=True)
print(tri.development_grain)   # -> 'M', but with int years it's 'Y'

The regex runs on the raw column, and astype(str) turns 2008.0 into "2008.0", which fails \d{4}. It then drops into the else branch, and since %Y parses to Jan 1 (year start), the month-end vs year-end alignment check fails and it picks M. Casting the column first, e.g. data[col].astype("Int64").astype(str), or stripping a trailing .0, fixes it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very intentional way wrongly coding dates lol, but easy fix, I'll do that, thank you!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh something broke, let me put this in draft for now. Will fix later

Comment thread chainladder/core/triangle.py Outdated
if len(development_date.unique()) == 1:
if len(data) == 1 and self.origin_grain.split("-")[0] in ["Y", "A"]:
# checks if development has any non-yearly values
dev_has_no_month = not bool(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional nitpick: the double negative not bool(development and not ...) is a bit hard to follow at first glance. might be clearer as an if/else block, but your call!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will rewrite this, good call here. I'll also improve it to loop over it instead of checking only the first index. Though I don't think this is really necessary

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rewrite reads a lot better, and good call looping over all the development columns. One question: Shall I keep flagging small readability nitpicks, or focus only on functional and correctness issues?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's up to you.

For me, I often rely on AI as little as possible, or even use weaker models on purpose to force me to learn (and because I'm cheap/poor lol). I sense that both these comments have some degree of AI involvement there, and I have no problem with that, but I personally wouldn't use AI to bring these up or catch these myself. I guess my point is if you think it's useful to have these discussions (I think yes to some degree) and you are learning, then yes bring them up. I did learn something through these discussions though. Now should we thrive for perfection and resolve all of bots comments 100%? I think that could be silly and pointless. Just my humble opinion :)

If you read the governing doc, I try to put this in there. We don't need to be perfectionists, if someone runs into a bug on production (the other comment) then yes we should def fix it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks kenneth! i checked out the governing doc and completely agree with prioritizing real production bugs over perfectionism on small details.

i do use AI to cross verify ideas while learning, but your tips are super helpful. i'll definitely take your advice to stick to smaller models so i can learn more on my own!

thanks again for sharing your perspective, i'll stick strictly to functional correctness for future reviews!

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dd248a1. Configure here.

Comment thread chainladder/core/triangle.py Outdated
@kennethshsu
kennethshsu marked this pull request as draft August 5, 2026 16:02
@kennethshsu

Copy link
Copy Markdown
Member Author

@priyam0k this is ready now, the way that I'm handling it is to cast to int and then string, kinda ugly and not sure if there's a better way. But I added your example as a test too.

By the way on the other commend re AI use. Please don't feel like I don't appreciate the feedback, I actually do, and I learned something, but I leave it up to you to decide on how much you use it and what feedback you want to share from your future work.

@kennethshsu
kennethshsu marked this pull request as ready for review August 6, 2026 00:00
@priyam0k

priyam0k commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

cast to int and then string, kinda ugly and not sure if there's a better way

nice way when cleaning up float-represented year columns, so nothing ugly. Glad the example was helpful.

Please don't feel like I don't appreciate the feedback, I actually do, and I learned something,

Never, I took all your note completely as constructive feedback. Since the experience gap is real, I always value hearing your perspective

I'm always looking to try (and even fail) mutiple times, so one day i can finally master it. Here is one meme I often relate, :)
image

on small nitpicks it is so discretionary to pick up relevant ones, I think only experience can reduce it, ...

@kennethshsu

Copy link
Copy Markdown
Member Author

Sounds good and thanks for the review here!

@henrydingliu @genedan can one of you do another secondary review? Will need this merged to unblock #1121, thanks guys!

@priyam0k
priyam0k merged commit 6be183d into main Aug 10, 2026
22 checks passed
@priyam0k
priyam0k deleted the #1192_1D_tri_valuation_date branch August 10, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Valuation should default to annual when origin grain is annual

3 participants