Skip to content

Mobile Payment Adoption

Task type: BinaryClassificationTask Industry: Fintech / Banking

Driving adoption of mobile payment features is a key growth lever for banking apps. New users who do not try the feature within the first two weeks are far less likely to adopt it later. By predicting which new users will adopt mobile payments, marketing teams can target the remaining users with tailored onboarding flows, tutorials, or promotional incentives during the critical first-impression window.

What makes this advanced? New-user lifecycle detection — the target function checks history to confirm the user has never used mobile payments before, ensuring only genuinely new-to-feature users enter the training set, then monitors a tight 14-day future window for adoption events.


Prerequisites

Before writing a target function you need:

  • A trained foundation model built on event data that includes the relevant data sources.
  • The monad library installed in your environment.
  • Data source(s): mobile_payments

Target Function

The target function tells monad how to label each entity for training. It receives four arguments:

Argument Type Description
history Events All events before the temporal split.
future Events All events after the temporal split.
attributes Attributes Static entity attributes.
ctx Dict Context dictionary containing SPLIT_TIMESTAMP, data mode, etc.

The function must return one of:

  • np.array([1], dtype=np.float32)positive case
  • np.array([0], dtype=np.float32)negative case
  • Noneexclude this entity from training

Full Example

Python
import numpy as np
from datetime import timedelta
from typing import Dict

from monad.ui.target_function import Events, Attributes
from monad.ui.target_function import SPLIT_TIMESTAMP
from monad.ui.target_function import has_incomplete_training_window


# === Configuration ===
TARGET_WINDOW_DAYS = 14
MOBILE_PAYMENTS_DATA_SOURCE = "mobile_payments"

def mobile_payment_adoption_target_fn(
    history: Events,
    future: Events,
    attributes: Attributes,
    ctx: Dict,
) -> np.ndarray | None:
    """Predict if a new user adopts mobile payments within 14 days."""

    split_ts = ctx[SPLIT_TIMESTAMP]

    if has_incomplete_training_window(ctx, timedelta(days=TARGET_WINDOW_DAYS)):
        return None

    # 1. Only target users who haven't used mobile payments before
    if history[MOBILE_PAYMENTS_DATA_SOURCE].count() > 0:
        return None

    # 2. Trim future to the 14-day adoption window
    future = future.interval_from(split_ts, timedelta(days=TARGET_WINDOW_DAYS))
    future_mobile = future[MOBILE_PAYMENTS_DATA_SOURCE]

    # 3. Label: adopted (1) or not (0)
    adopted = 1 if future_mobile.count() > 0 else 0
    return np.array([adopted], dtype=np.float32)

Step-by-Step Breakdown

① Filter to new users only

Python
if history[MOBILE_PAYMENTS_DATA_SOURCE].count() > 0:
    return None

Users who already have mobile payment events in their history are excluded by returning None. This ensures the model only trains on users who are genuinely new to the feature, avoiding contamination from already-adopted users who would trivially be labeled positive.

② Trim future window

Python
future = future.interval_from(split_ts, timedelta(days=TARGET_WINDOW_DAYS))
future_mobile = future[MOBILE_PAYMENTS_DATA_SOURCE]

The future events are restricted to exactly 14 days from the split timestamp. This creates a tight adoption window that aligns with the business goal — catching users in their critical first two weeks.

③ Check for adoption events

Python
adopted = 1 if future_mobile.count() > 0 else 0
return np.array([adopted], dtype=np.float32)

If any mobile payment event exists in the 14-day window, the user is labeled as adopted (positive). Otherwise, they are labeled as not adopted (negative). The simplicity here is intentional — any usage counts as adoption.


Training

Once the target function is defined, fine-tune a downstream model:

Python
from pathlib import Path
from monad.ui.config import TrainingParams, MetricParams, MetricMonitoringMode
from monad.config.early_stopping import EarlyStopping

from monad.ui.module import load_from_foundation_model, BinaryClassificationTask

module = load_from_foundation_model(
    checkpoint_path=Path("./foundation_model"),
    downstream_task=BinaryClassificationTask(),
    target_fn=mobile_payment_adoption_target_fn,
)

training_params = TrainingParams(
    checkpoint_dir=Path("./<this_model>"),
    learning_rate=1e-4,
    epochs=20,
    devices=[0],
    metrics=[
        MetricParams(alias="auroc", metric_name="AUROC", kwargs={"task": "binary"}),
        MetricParams(alias="auprc", metric_name="AveragePrecision", kwargs={"task": "binary"}),
        MetricParams(alias="recall", metric_name="Recall", kwargs={"task": "binary"}),
        MetricParams(alias="precision", metric_name="Precision", kwargs={"task": "binary"}),
    ],
    metric_to_monitor="val_auroc_0",
    metric_monitoring_mode=MetricMonitoringMode.MAX,
    early_stopping=EarlyStopping(min_delta=1e-4, patience=5),
)

module.fit(training_params, seed=42)

Evaluation

Python
from pathlib import Path
from datetime import datetime, timezone
from monad.ui.module import load_from_checkpoint
from monad.ui.config import TestingParams, MetricParams, OutputType

module = load_from_checkpoint(Path("./<this_model>"))

testing_params = TestingParams(
    prediction_date=datetime(2024, 5, 1, tzinfo=timezone.utc),
    output_type=OutputType.DECODED,
    devices=[0],
    metrics=[
        MetricParams(alias="auroc", metric_name="AUROC"),
        MetricParams(alias="auprc", metric_name="AveragePrecision"),
        MetricParams(alias="recall", metric_name="Recall"),
    ],
)

results = module.test(testing_params)

Prediction

Python
from pathlib import Path
from datetime import datetime, timezone
from monad.ui.module import load_from_checkpoint
from monad.ui.config import TestingParams, OutputType

module = load_from_checkpoint(Path("./<this_model>"))

testing_params = TestingParams(
    local_save_location=Path("./predictions.tsv"),
    output_type=OutputType.DECODED,
    prediction_date=datetime(2024, 6, 1, tzinfo=timezone.utc),
    devices=[0],
)

predictions = module.predict(testing_params)

Metric Why it matters
AUROC Measures overall ranking quality.
AUPRC More informative when the positive class is rare.
Recall Proportion of actual positives caught.
Precision Proportion of predicted positives that are correct.
F1 Score Harmonic mean of precision and recall.

Production Tips

  1. Define "new user" precisely. The current function uses zero historical mobile payment events as the criterion. Depending on your product, you may also want to filter by account age (e.g., only users who registered in the last 90 days) to avoid including dormant accounts.
  2. Adjust the adoption window to match your onboarding cadence. If your app sends onboarding nudges over 7 days, a 14-day window gives enough buffer. If onboarding is longer, extend the window accordingly.
  3. Segment predictions by user cohort. New users acquired through different channels (organic, paid, referral) may have very different adoption rates. Consider building channel-specific models or adding acquisition channel as a feature.
  4. Re-score users frequently. Run predictions daily or weekly so that users who are approaching the end of their adoption window without converting receive escalated interventions.
  5. Track downstream impact. Measure not just model accuracy but the actual lift in mobile payment adoption rate among users who received targeted campaigns based on model predictions.