Nested context and account outcomes
The repeat-payment example predicts two outcomes for each transaction in an account history. An account review team also needs to know how much loss to expect across the account over the next month.
Keep the transaction sequence, including the latest payment once, and add more account context. Purchases now have their own line items; cash withdrawals form another event history with different fields. This advanced example combines nested context, transaction predictions, and an account-level target.
One observation
account_id: account-42
account_age_days: 420
trxns:
- booked_at: "2026-03-28T09:00:00"
amount: 24.00
merchant: Harbor Internet
channel: direct_debit
line_items:
- sku: internet-plan
quantity: 1
disputed: false
- booked_at: "2026-04-28T09:00:00"
amount: 24.00
merchant: Harbor Internet
channel: direct_debit
line_items:
- sku: internet-plan
quantity: 1
disputed: false
- booked_at: "2026-05-27T08:00:00"
amount: 32.00
merchant: Station Cafe
channel: card
line_items:
- sku: lunch
quantity: 2
disputed: true
- booked_at: "2026-05-28T09:00:00"
amount: 24.00
merchant: Harbor Internet
channel: direct_debit
line_items:
- sku: internet-plan
quantity: 1
disputed: false
recent_cash_withdrawals:
- booked_at: "2026-05-26T16:00:00"
amount: 100.00
atm_region: home_region
loss_next_30_days: 0.0One record describes an account at a decision time. Profile attributes, transactions, line items, and withdrawals contain only information available then. Purchase baskets can distinguish activity at the same merchant; cash withdrawals provide another view of account behavior without inventing a merchant or line items for an ATM event.
The cutoff is the final booking on May 28 at 09:00, following Prepare observations. Each disputed label covers the 30 days after it, and loss_next_30_days records total account loss in the application’s currency over the same window.
Place each target with its subject
import relflow as rf
model = rf.Model.md(
account_age_days=rf.Number,
trxns=rf.Branch(
length=64,
overflow="tail",
booked_at=rf.DateParts(
dateparts=["day_of_month", "day_of_week", "hour_of_day"],
),
amount=rf.Number,
merchant=rf.Category,
channel=rf.Enum(values=("card", "direct_debit", "bank_transfer")),
line_items=rf.Branch(
length=16,
sku=rf.Category,
quantity=rf.Number,
),
disputed=rf.Boolean(mask=True),
),
recent_cash_withdrawals=rf.Branch(
length=16,
overflow="tail",
booked_at=rf.DateParts(
dateparts=["day_of_month", "day_of_week", "hour_of_day"],
),
amount=rf.Number,
atm_region=rf.Category,
),
loss_next_30_days=rf.Number(mask=True),
)Line items form context within each transaction. The transaction sequence then sends its reduced representation to the account root, alongside the withdrawal history. The dispute decoder uses its ancestor context and aligned visible siblings; its output retains the transaction coordinates. See Model tree.
Both targets use mask=True. Every transaction’s dispute label and the account-loss label supply supervision while staying out of the inputs.
At prediction time, omit loss_next_30_days and omit disputed from every transaction. Keep the transaction, line-item, and withdrawal records known at the decision time; they define the positions and context to predict.
Preserve the context the task needs
The md preset uses Attention reductions with four summaries per parent coordinate. Those summaries bound the context sent upward; they are not one token per original line item. If matching a SKU across transactions matters, represent the shared relationship through aligned input or preprocessing. Nesting does not join database tables.
overflow="tail" assumes transactions are sorted oldest to newest. The line items keep the default first-items policy. Withdrawals are also oldest to newest and retain their own last 16 records. The two histories have independent lengths; matching list positions do not imply related events. Choose each capacity from the records the application needs to model.
Fit the same way as earlier examples, using files prepared for this richer account schema:
data = rf.ArrowDataModule(
train="accounts/risk/train.parquet",
validate="accounts/risk/validation.parquet",
)
model.fit(data, batch_size=128, max_epochs=30)Evaluate the two outcomes
Fit using data modules and Training options. Hold out later decision windows and control overlap between related account snapshots.
Compare the transaction-only model, the addition of line items, and the addition of withdrawals before attributing value to the complete schema. Inspect dispute ranking and calibration separately from account-loss errors. Their combined validation loss can hide a regression in one target. This example specifies a model design; it makes no claim about measured quality.
Every transaction’s dispute result is at predictions.trxns[i].disputed, and the account loss is predictions.loss_next_30_days. All retained dispute positions have inferred: true. Retain account_id and the application’s transaction identifiers when joining outputs back to records. Account for the retained history window: an attention summary is not a transaction identifier. See Prediction output.
Next, when labels are scarce, pretrain on the observed transaction fields before adapting the model to an outcome. That guide changes the learning objective while keeping the payment-history modeling pattern.