Rules Reference
Every rule the engine can run. A rule is a method on rules.RuleReflector dispatched by name
(rule_arn); implementations live in pkg/rules/rule_<snake_name>.go. Rules are pure functions
of RuleExecutionData — they make no network or GrowthBook calls. Result encoding:
DynamoDB.
Rulebook rules
The 25 rules configurable in rulebooks (scripts/yaml_rulebooks/rules.yml); which rulebooks use
them: Rulebooks.
| Rule | Passes when | Custom props |
|---|---|---|
RuleAgeOfAccount |
Oldest transaction implies account age >= |
|
RuleAverageBalance |
Mean daily available balance >= threshold (passes without balance data) |
|
RuleBalanceBetweenBounds |
User has float rank >= |
|
RuleBalanceRequirement |
Available or current balance >= threshold; optional float-count gate |
|
RuleCollectionsErrors |
Collection-error : completed-float ratio ⇐ |
|
RuleCompetitorEwa |
Competitor-EWA borrow/repayment counts in the window meet minimums |
|
RuleConsecutiveSubscriptions |
Most recent |
|
RuleEssentialSpend |
Enough qualifying transactions in essential categories within the window |
|
RuleEWADollarAmount |
Competitor-EWA borrowed and repaid dollar totals meet minimums |
|
RuleFloatRank |
Completed-float count within |
|
RuleGoodStanding |
Active user, no outstanding float, no open loan — see RuleGoodStanding |
— |
RuleInstitutionCheck |
User not at a listed institution, or balance >= |
|
RuleLowTransactions |
Average transactions/day >= threshold over the window, with float-rank exemption |
|
RuleMLPaybackPrediction |
ML default probability within threshold; optional float-count scoping |
|
RuleMLPaybackPredictionVariableThreshold |
Same, threshold picked by completed-float count |
|
RuleMultipleAccounts |
Related-account count ⇐ |
|
RuleOnTimeFloatPayback |
Last N floats repaid within the grace period |
|
RulePayrollSpend |
Post-payroll spend within |
|
RuleRecentFloat |
Last float repaid within |
|
RuleRecurringDepositsAndHighTransfer |
Recurring income present and payday transfers ⇐ |
|
RuleSpendVelocity |
At most |
|
RuleSubscriptionRank |
Subscription rank >= |
|
RuleSuspiciousHighBalance |
Not (balance > threshold on an account younger than |
|
RuleTransferRatio |
Transfer transactions ⇐ |
|
RuleValidDebitCard |
User has a valid debit card — see RuleValidDebitCard for the skip cases |
— |
Source: pkg/rules/, scripts/yaml_rulebooks/rules.yml
The two ML rules above run inside rulebooks against the rule-ML SageMaker endpoint. They are
unrelated to Payback Predictor V2, which runs in the API Lambda
after the rule engine has decided and can override that decision. The rule runner calls the
endpoint once per run, before any rulebook is evaluated, only when SAGEMAKER_ENABLED is true
(Infrastructure); otherwise both rules return an error
outcome.
Model-feature rules
Registered in pkg/rulerunner/model_feature_rules.go and merged into every rule run regardless
of rulebook configuration. Their outcomes feed the payback predictor v2 cold
features (ML Feature Pipeline); the stored name is the rule ARN
plus a V2/V3 suffix, so they never collide with a rulebook’s own registration.
| Stored name | Rule | Notes |
|---|---|---|
RuleTransactionCategoryFeaturesV3 |
RuleTransactionCategoryFeatures |
Feature-only: category and cash-flow aggregates; always approves |
RuleBalanceHistoryV2 |
RuleBalanceHistory |
Feature-only: balance-history metrics; always approves |
RuleSubscriptionRankV3 |
RuleSubscriptionRankModelFeature |
Feature-only: parses the CFI-time sub rank out of |
RuleGoodStandingV2 |
RuleGoodStanding |
Good-standing gate outcome for the model |
RuleMLPaybackPredictionV2 |
RuleMLPaybackPrediction |
Threshold 0.0 (feature capture) |
RuleFloatRankV3 |
RuleFloatRank |
|
RuleCompetitorEwaV2 |
RuleCompetitorEwa |
32-day window, zero minimums |
RuleBalanceRequirementV2 |
RuleBalanceRequirement |
Zero thresholds |
RuleTransferRatioV2 |
RuleTransferRatio |
32-day window, 100% cap |
RuleLowTransactionsV2 |
RuleLowTransactions |
32-day window, 1.6/day |
RuleCollectionsErrorsV2 |
RuleCollectionsErrors |
Ratio cap 1.5 |
RuleAverageBalanceV2 |
RuleAverageBalance |
Threshold 0 |
RuleEssentialSpendV2 |
RuleEssentialSpend |
Groceries category, zero minimums |
Source: pkg/rulerunner/model_feature_rules.go
RuleFloatRank borrow-history features
Beyond its pass/deny result, RuleFloatRank emits borrow-history features onto its outcome. The
first four feed the payback predictor v2 cold features; the last two exist for the V2 CFI
guardrails and are ignored by the model. Any of them is null when the user has no history to
derive it from.
The active SCHEDULING float with no payback date is excluded throughout, so these describe only historical borrows.
| Feature | Unit | Meaning |
|---|---|---|
|
count |
Prior borrows in any status |
|
ratio |
Share of prior borrows repaid on or before their due date |
|
dollars |
Largest amount ever repaid |
|
days |
Age of the newest payback date; |
|
dollars |
Amount of the single most recently repaid float |
|
days |
That float’s payback date minus its due date — negative when repaid early, |
The last two describe one float rather than an aggregate, so they can disagree with
prior_borrow_max_repaid: a user whose biggest float was two borrows ago reports the smaller,
newer amount here. Both are null together, and only for a user with no completed float the rule
can date — which covers the new user and the user whose prior float defaulted, since a float that
was never repaid has no payback date. Two floats sharing a payback date resolve to the larger
amount, so the pair never depends on the order float-service returns.
last_repaid_days_late is measured against the due date truncated to a day, matching the on-time
definition behind prior_borrow_on_time_rate: the due date arrives as an RFC3339 timestamp with a
time of day while the payback date is date-only, so comparing them raw would read almost every
repayment as a day early.
Source: pkg/rules/rule_float_rank.go (computeFloatBorrowFeatures)
RuleGoodStanding
Passes when all of:
-
user status is
ACTIVEorPROCESSING— orPAUSEDon areactivationinvocation (RuleExecutionData.InvocationType, set by the instant check); -
no outstanding (non-COMPLETED, non-HOLD) float — with two carve-outs below;
-
no open loan.
Reactivation carve-out: on a reactivation run, outstanding floats whose debit date has passed
are discounted — the reactivation charge repays them (repaidOnReactivation, mirroring
float-service’s payback). A float due in the future still blocks, and open loans are never
discounted.
Early-advance carve-out: the rule discounts one ACHSENT float for a user who cleared an
early-advance path and is in the treatment arm; for the float_rank path the rule runs the
qualification checks itself. On the passing branch it emits the grant keys, and on both branches
the experiment diagnostics. Full design: Early Advance.
Source: pkg/rules/rule_good_standing.go
RuleValidDebitCard
Passes when the user’s debit card is valid; a nil card denies with NODATA. The rule
auto-passes for PROCESSING users and for PAUSED/ACTIVE users on a reactivation
invocation: the reactivation charge is itself the card check — user-service repays the
outstanding float and subscription by debit, so a member whose card cannot be charged never
completes reactivation. The card is checked normally on every later event-driven run.
Source: pkg/rules/rule_valid_debit_card.go
Error handling
A rule that errors records error: true with an error_code feature (CALCERR for missing
input data) and denies; the rest of the rulebook still runs. How errors roll up to statuses:
Rule Engine.