While there are good solutions in this thread, some of them have scaling issues with operator overhead.
We recently implemented a strategy that I proposed a couple years ago that uses a bucket system.
We created 5 or 6 different buckets of limit values (for v4 and v6 of course.) Depending on what you have published in PeeringDB (or told us directly what to expect), you're placed in a bucket that gives you a decent amount of headroom to that bucket's max. If your ASN reaches 90% of your limit, our ops folks just move you up to the next bucket. If you start to get up there in the last bucket, then we'll take a manual look and decide what is appropriate. This covers well over 95% of our non-transit sessions, and has dramatically reduced the volume of tickets and changes our ops team has had to sort through.
Of course, we can also afford to be a little looser in limits based on the capability of the equipment that these sessions land on, other environments may require tighter restrictions.