feat(charge_filters): add code to identify filters - #6079
Merged
Conversation
annvelents
reviewed
Aug 5, 2026
annvelents
reviewed
Aug 5, 2026
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 7, 2026 10:31
8dbb5a2 to
43ac397
Compare
annvelents
reviewed
Aug 7, 2026
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 7, 2026 10:35
43ac397 to
ec9a03a
Compare
annvelents
reviewed
Aug 7, 2026
annvelents
reviewed
Aug 7, 2026
annvelents
reviewed
Aug 7, 2026
brunomiguelpinto
marked this pull request as ready for review
August 7, 2026 15:27
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 7, 2026 15:30
7cc77fe to
10789fd
Compare
endenis
approved these changes
Aug 10, 2026
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 10, 2026 12:16
10789fd to
2366516
Compare
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 10, 2026 12:34
2366516 to
266b88e
Compare
annvelents
reviewed
Aug 10, 2026
annvelents
approved these changes
Aug 10, 2026
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 10, 2026 14:26
c871f89 to
6945a6c
Compare
## Context A charge filter has no stable identity. It is known by its values, and those move: removing a value from a billable metric trims it out of every filter using it, so two filters that were distinct can collapse onto the same values and stop being distinguishable. Propagating a plan's charge to its overrides pairs filters by those values, so once that happens it can reprice the wrong one. ## Description Charge filters get a code, derived from the values a filter is created with and frozen from then on, so a filter keeps its identity even when its values move. An override inherits the code of the filter it was copied from rather than deriving its own, and that inheritance is what records the link between the two. A filter that exists only on the override derives its own code, since there is nothing to inherit from. A partial unique index keeps the code unique per charge among the filters that are not deleted. Existing rows are backfilled by one walk over the charges belonging to a plan, a job per charge, each handing its own overrides on to a second job as it finishes. That second job pairs a copy back to the filter it came from by a signature the database computes: one entry per billable metric filter a charge filter names, holding that filter's values sorted. Two filters are the same filter when their signatures are equal. Three kinds of filter are left without a code on purpose: a charge holding two filters on one predicate, because whichever kept the code would be the one that bills from then on; an override holding a filter its plan no longer has, because there is nothing left to link it to; and one whose code a sibling already holds, frozen when that sibling's own values were these. The upgrade task therefore waits on the count of codeless filters to stop moving rather than on it reaching zero, and filters:report_codes reports what was left and why.
brunomiguelpinto
force-pushed
the
ing-536-filter-code
branch
from
August 10, 2026 19:14
5df941f to
e580c79
Compare
annvelents
reviewed
Aug 11, 2026
annvelents
approved these changes
Aug 11, 2026
annvelents
left a comment
Contributor
There was a problem hiding this comment.
Agree on @tiagolupepic's comments, but LGTM! 🚀
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
A charge filter has no stable identity. It is known by its values, and those move: removing a value
from a billable metric trims it out of every filter using it, so two filters that were distinct can
collapse onto the same values and stop being distinguishable.
Propagating a plan's charge to its overrides pairs filters by those values, so once that happens it
can reprice the wrong one.
Description
Charge filters get a
code, derived from the values a filter is created with and frozen from thenon, so a filter keeps its identity even when its values move. An override inherits the code of the
filter it was copied from rather than deriving its own — that inheritance is what records the link
between the two.
A filter that exists only on the override, because the customer negotiated a segment the plan
does not price, derives its own code: there is nothing to inherit from, and it is not orphaned
either. If the plan later gains that predicate it derives the same code, so the two line up by
construction rather than by luck.
Backfilling existing rows
One walk, over the charges belonging to a plan, a job per charge. Each job derives the codes for its
own filters and then hands its overrides on to a second job
The unit of work is a charge on the way in and a parent on the way out, because every override of a
charge answers to the same set of codes.
What stays NULL, on purpose
bills from then on, and that is not a migration's call. Only the colliding filters are skipped;
their siblings are still filled.
because the plan dropped it or because the parent charge was deleted and
dependent: :nullifycutthe link. Damage already done; no backfill undoes it.
writing it again would break the unique index on
(charge_id, code).rake filters:report_codesreports what is left and why. Cleaning up the first kind and running theupgrade task again converges, since a cleaned charge goes back to having filters without a code and
the walk selects it again.