Skip to content

chore: split rating service#4199

Merged
turip merged 3 commits intomainfrom
chore/split-rating
Apr 21, 2026
Merged

chore: split rating service#4199
turip merged 3 commits intomainfrom
chore/split-rating

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 21, 2026

Overview

Note: code duplication is intentional, these two methods will diverge over time.

Split openmeter/billing/charges/usagebased/service/rating into GetTotalsForUsage in totals.go and GetDetailedLinesForUsage in details.go, with comments documenting the return values and that totals-only lookup
should be preferred because it is faster.

Refactored the totals-only consumers to use GetTotalsForUsage in realtime expand (openmeter/billing/charges/usagebased/service/get.go) and GetCurrentTotals (openmeter/billing/charges/usagebased/service/ currenttotals.go), while state-machine/run paths now call GetDetailedLinesForUsage, and I reverted the temporary ledger API changes.

Updated openmeter/billing/charges/service/featureid_test.go to assert on due totals instead of quantity, then ran focused verification successfully: go test -count=1 -tags=dynamic ./openmeter/billing/charges/service ./ openmeter/billing/charges/usagebased/service ./openmeter/billing/charges/usagebased/service/run ./openmeter/billing/charges/usagebased/service/rating.

Notes for reviewer

Summary by CodeRabbit

  • Refactor
    • Internal billing/rating reorganized to separate detailed line generation from totals computation for clearer calculations.
  • Behavioral
    • Usage responses now emphasize aggregated monetary totals (due totals/charge) and no longer include raw quantity in current totals.
  • Tests
    • Updated a usage-based activation test to assert monetary totals instead of quantity.

@turip turip requested a review from a team as a code owner April 21, 2026 18:51
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a25f5478-70c4-4405-8d24-2ef10d3a1f91

📥 Commits

Reviewing files that changed from the base of the PR and between 8ce36de and 791abcd.

📒 Files selected for processing (2)
  • openmeter/billing/charges/usagebased/service.go
  • openmeter/billing/charges/usagebased/service/get.go
💤 Files with no reviewable changes (1)
  • openmeter/billing/charges/usagebased/service.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • openmeter/billing/charges/usagebased/service/get.go

📝 Walkthrough

Walkthrough

Replaces the single rater method GetRatingForUsage with two specialized methods: GetDetailedLinesForUsage (returns detailed lines + snapshot quantity) and GetTotalsForUsage (returns totals only). Call sites were updated: callers needing quantity use the detailed variant; callers needing only totals use the totals variant.

Changes

Cohort / File(s) Summary
Rating service removal
openmeter/billing/charges/usagebased/service/rating/service.go
Removed the old GetRatingForUsage implementation and the now-unused context import.
Detailed-lines method
openmeter/billing/charges/usagebased/service/rating/details.go
Added GetDetailedLinesForUsage which validates input, snapshots quantity, and returns generated detailed rating lines plus the snapshot quantity.
Totals-only method
openmeter/billing/charges/usagebased/service/rating/totals.go
Added GetTotalsForUsage which validates input, snapshots quantity, generates detailed lines, and returns totals.Totals.
State machines & run creation (detailed results)
openmeter/billing/charges/usagebased/service/creditheninvoice.go, openmeter/billing/charges/usagebased/service/creditsonly.go, openmeter/billing/charges/usagebased/service/run/create.go
Replaced calls to GetRatingForUsage with GetDetailedLinesForUsage; logic continues to use returned totals and Quantity where needed.
Totals-only call sites / realtime expand
openmeter/billing/charges/usagebased/service/currenttotals.go, openmeter/billing/charges/usagebased/service/get.go
Replaced calls to GetRatingForUsage with GetTotalsForUsage; callers now consume totals.Totals directly and no longer expect Quantity.
Public API struct change
openmeter/billing/charges/usagebased/service.go
Removed Quantity field from GetCurrentTotalsResult, and dropped the unused alpacadecimal import.
Tests
openmeter/billing/charges/service/featureid_test.go
Adjusted assertion to check currentTotals.DueTotals.Total == 21 instead of currentTotals.Quantity == 7.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

release-note/misc

Suggested reviewers

  • GAlexIHU
  • tothandras
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: split rating service' accurately describes the main objective of the PR, which splits the monolithic rating service into two focused methods (GetTotalsForUsage and GetDetailedLinesForUsage) across separate files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/split-rating

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@turip turip added release-note/ignore Ignore this change when generating release notes area/billing labels Apr 21, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
openmeter/billing/charges/usagebased/service/run/create.go (1)

112-120: Tiny nit: the error wrap still says "get rating for usage".

Not a bug, just a stale breadcrumb — the call is now GetDetailedLinesForUsage, so an error here reads a little confusingly in logs. Feel free to ignore if you want to keep the message stable for grep-ability across history.

🧽 Optional wording tweak
-		return CreateRatedRunResult{}, fmt.Errorf("get rating for usage: %w", err)
+		return CreateRatedRunResult{}, fmt.Errorf("get detailed lines for usage: %w", err)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/usagebased/service/run/create.go` around lines 112
- 120, The error wrap message is stale: change the returned error in the
GetDetailedLinesForUsage call so it reflects the current method name; replace
fmt.Errorf("get rating for usage: %w", err) with a message like fmt.Errorf("get
detailed lines for usage: %w", err) (in the block where
s.rater.GetDetailedLinesForUsage is called and returning CreateRatedRunResult{}
on error) so logs correctly describe s.rater.GetDetailedLinesForUsage failures.
openmeter/billing/charges/usagebased/service/rating/totals.go (1)

11-36: Docstring overstates the optimization, and there's near-duplicate logic with details.go.

Two small things worth tightening up here:

  1. The comment says "It avoids generating detailed lines, so prefer it over GetDetailedLinesForUsage when only totals are needed." But the body still calls s.ratingService.GenerateDetailedLines(...) — the detailed lines are generated, they're just thrown away. Functionally fine, but the docstring sets an expectation the code doesn't meet. Either update the comment to be honest ("returns only the totals portion of the rating result") or swap in a cheaper totals-only path on ratingService if one exists.

  2. Everything above the final return is identical to GetDetailedLinesForUsage in details.go (validate → snapshotQuantityGenerateDetailedLines). A tiny shared helper would keep the two APIs from drifting.

♻️ One way to dedupe
// in a shared file (e.g. rating/service.go)
func (s *Service) rate(ctx context.Context, in GetRatingForUsageInput) (billingrating.GenerateDetailedLinesResult, alpacadecimal.Decimal, error) {
    if err := in.Validate(); err != nil {
        return billingrating.GenerateDetailedLinesResult{}, alpacadecimal.Decimal{}, err
    }

    q, err := s.snapshotQuantity(ctx, snapshotQuantityInput{
        Customer:       in.Customer.Customer,
        FeatureMeter:   in.FeatureMeter,
        ServicePeriod:  in.Charge.Intent.ServicePeriod,
        StoredAtOffset: in.StoredAtOffset,
    })
    if err != nil {
        return billingrating.GenerateDetailedLinesResult{}, alpacadecimal.Decimal{}, fmt.Errorf("get snapshot quantity: %w", err)
    }

    res, err := s.ratingService.GenerateDetailedLines(usagebased.RateableIntent{
        Intent:     in.Charge.Intent,
        MeterValue: q,
    })
    if err != nil {
        return billingrating.GenerateDetailedLinesResult{}, alpacadecimal.Decimal{}, fmt.Errorf("rating: %w", err)
    }
    return res, q, nil
}

Then both GetDetailedLinesForUsage and GetTotalsForUsage become 3-liners.

As per coding guidelines: "In general when reviewing the Golang code make readability and maintainability a priority, even potentially suggest restructuring the code to improve them." And re: performance — since rating sits on a billing hot path, the docstring currently promising "avoids generating detailed lines" is the kind of thing that'll cause a reviewer to mis-pick the "faster" API.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/usagebased/service/rating/totals.go` around lines
11 - 36, The docstring for GetTotalsForUsage incorrectly claims it avoids
generating detailed lines while the body calls
s.ratingService.GenerateDetailedLines; update the comment to state it "returns
only the totals portion of the rating result" (or, if a totals-only method
exists on ratingService, call that instead). Also factor the duplicated logic
(input validation → snapshotQuantity → GenerateDetailedLines) into a shared
helper on Service (e.g., rate or rateForUsage) and have both GetTotalsForUsage
and GetDetailedLinesForUsage call that helper to return the rating result and
snapshot quantity, keeping error wrapping behavior (use the same error messages
like "get snapshot quantity" and "rating" or "rating totals") consistent.
openmeter/billing/charges/usagebased/service/creditheninvoice.go (1)

186-194: Same stale error wording as in run/create.go.

The method is now GetDetailedLinesForUsage but the wrap still reads "get rating for usage". Worth a matching rename for log consistency, or leave it if you prefer stable message strings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/charges/usagebased/service/creditheninvoice.go` around
lines 186 - 194, The error wrap message is stale: when calling
s.Rater.GetDetailedLinesForUsage(...) the returned error is wrapped with "get
rating for usage"; update the wrap to reflect the actual method name (e.g., "get
detailed lines for usage" or similar) for consistency and easier tracing—locate
the call to GetDetailedLinesForUsage in creditheninvoice.go (the block that
assigns ratingResult, err) and change the fmt.Errorf string accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/billing/charges/usagebased/service/currenttotals.go`:
- Around line 61-64: GetCurrentTotalsResult is returned without populating
Quantity causing TestChargeFeatureIDs to fail; update the logic in
GetCurrentTotals (where you currently call GetTotalsForUsage) to call
GetDetailedLinesForUsage instead and extract the metered amount from the
returned GetRatingForUsageResult.Quantity, then set
GetCurrentTotalsResult.Quantity before returning; alternatively if you
intentionally want to drop Quantity, remove the field from
GetCurrentTotalsResult and adjust tests, but to satisfy current tests implement
the first option using GetDetailedLinesForUsage and the Quantity symbol from
GetRatingForUsageResult.

In `@openmeter/billing/charges/usagebased/service/get.go`:
- Around line 177-190: The two error returns in the mapping lambda are
indistinguishable; change the second error (the type-assertion failure after
dueTotalsAny.(totals.Totals)) to a distinct message indicating an invalid type
(e.g., "invalid totals type for charge %s") so callers can tell apart a missing
entry from a wrong-type entry; locate the anonymous function passed to
slicesx.MapWithErr (using ratingResults.Load, charge.GetChargeID(),
totals.Totals, and charge.Expands.RealtimeUsage) and update only the fmt.Errorf
for the failed type assertion.

In `@openmeter/billing/charges/usagebased/service/rating/details.go`:
- Around line 10-12: The doc comment for GetDetailedLinesForUsage wrongly claims
GetTotalsForUsage is faster even though GetTotalsForUsage currently calls
s.ratingService.GenerateDetailedLines(...) and does the same work; either
remove/soften the "Prefer GetTotalsForUsage when only totals are needed because
it is faster" phrasing in the comment of GetDetailedLinesForUsage, or implement
a true fast path by changing GetTotalsForUsage to avoid calling
s.ratingService.GenerateDetailedLines (e.g., add a totals-only method on
ratingService or a flag to GenerateDetailedLines that returns only totals), and
update references to GetTotalsForUsage/GetDetailedLinesForUsage and
s.ratingService.GenerateDetailedLines accordingly.

---

Nitpick comments:
In `@openmeter/billing/charges/usagebased/service/creditheninvoice.go`:
- Around line 186-194: The error wrap message is stale: when calling
s.Rater.GetDetailedLinesForUsage(...) the returned error is wrapped with "get
rating for usage"; update the wrap to reflect the actual method name (e.g., "get
detailed lines for usage" or similar) for consistency and easier tracing—locate
the call to GetDetailedLinesForUsage in creditheninvoice.go (the block that
assigns ratingResult, err) and change the fmt.Errorf string accordingly.

In `@openmeter/billing/charges/usagebased/service/rating/totals.go`:
- Around line 11-36: The docstring for GetTotalsForUsage incorrectly claims it
avoids generating detailed lines while the body calls
s.ratingService.GenerateDetailedLines; update the comment to state it "returns
only the totals portion of the rating result" (or, if a totals-only method
exists on ratingService, call that instead). Also factor the duplicated logic
(input validation → snapshotQuantity → GenerateDetailedLines) into a shared
helper on Service (e.g., rate or rateForUsage) and have both GetTotalsForUsage
and GetDetailedLinesForUsage call that helper to return the rating result and
snapshot quantity, keeping error wrapping behavior (use the same error messages
like "get snapshot quantity" and "rating" or "rating totals") consistent.

In `@openmeter/billing/charges/usagebased/service/run/create.go`:
- Around line 112-120: The error wrap message is stale: change the returned
error in the GetDetailedLinesForUsage call so it reflects the current method
name; replace fmt.Errorf("get rating for usage: %w", err) with a message like
fmt.Errorf("get detailed lines for usage: %w", err) (in the block where
s.rater.GetDetailedLinesForUsage is called and returning CreateRatedRunResult{}
on error) so logs correctly describe s.rater.GetDetailedLinesForUsage failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a08cecb6-85b0-4860-a254-eb43a5c2f8c3

📥 Commits

Reviewing files that changed from the base of the PR and between fd573fa and 0776ce2.

📒 Files selected for processing (8)
  • openmeter/billing/charges/usagebased/service/creditheninvoice.go
  • openmeter/billing/charges/usagebased/service/creditsonly.go
  • openmeter/billing/charges/usagebased/service/currenttotals.go
  • openmeter/billing/charges/usagebased/service/get.go
  • openmeter/billing/charges/usagebased/service/rating/details.go
  • openmeter/billing/charges/usagebased/service/rating/service.go
  • openmeter/billing/charges/usagebased/service/rating/totals.go
  • openmeter/billing/charges/usagebased/service/run/create.go
💤 Files with no reviewable changes (1)
  • openmeter/billing/charges/usagebased/service/rating/service.go

Comment thread openmeter/billing/charges/usagebased/service/currenttotals.go
Comment thread openmeter/billing/charges/usagebased/service/get.go
Comment thread openmeter/billing/charges/usagebased/service/rating/details.go
@turip turip force-pushed the chore/split-rating branch from 588a903 to 791abcd Compare April 21, 2026 19:02
@turip turip enabled auto-merge (squash) April 21, 2026 19:06
@turip turip merged commit 087e9c1 into main Apr 21, 2026
27 of 28 checks passed
@turip turip deleted the chore/split-rating branch April 21, 2026 19:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing release-note/ignore Ignore this change when generating release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants