Skip to content

Biometric Login Enablement

Task type: BinaryClassificationTask Industry: Banking / Mobile Apps

Biometric login (fingerprint, face recognition) improves both security and user experience. Banks and fintech companies want to maximize adoption of this feature, but many customers leave it disabled after onboarding. By predicting which customers are likely to enable biometrics on their own, teams can focus outreach on the remaining users — sending tutorials, security reminders, or in-app prompts to those who would not enable it without a nudge.

What makes this advanced? Month-end date calculation — the target function dynamically computes the last timestamp of the current month to create a calendar-aligned prediction window, then filters settings events up to that boundary.


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): settings_events

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

from datetime import datetime

# === Configuration ===
SETTINGS_DATA_SOURCE = "settings_events"
OPTION_COLUMN = "option"
ACTION_COLUMN = "action"
TARGET_OPTION = "access.biometrics"
TARGET_ACTION = "enable"

def get_last_timestamp_of_month(timestamp: float) -> float:
    """Compute the last second of the month containing `timestamp`."""
    dt = datetime.fromtimestamp(timestamp)
    next_month = dt.replace(day=28, hour=23, minute=59, second=59) + timedelta(days=4)
    return (next_month - timedelta(days=next_month.day)).timestamp()

def biometric_login_target_fn(
    history: Events,
    future: Events,
    attributes: Attributes,
    ctx: Dict,
) -> np.ndarray | None:
    """Predict if customer enables biometric login by month-end."""

    split_ts = ctx[SPLIT_TIMESTAMP]

    # 1. Check if already enabled — no need to predict
    past_biometrics = history[SETTINGS_DATA_SOURCE].filter(
        OPTION_COLUMN, lambda option: option == TARGET_OPTION
    )
    if len(past_biometrics) and past_biometrics[ACTION_COLUMN].events[-1] == TARGET_ACTION:
        return np.array([0], dtype=np.float32)

    # 2. Compute month-end boundary
    month_end_ts = get_last_timestamp_of_month(split_ts)
    if split_ts == month_end_ts:
        return None

    # 3. Filter future events up to month-end
    month_window = timedelta(seconds=month_end_ts - split_ts)
    future_window = future.interval_from(split_ts, month_window)

    # 4. Check if biometric login was enabled
    biometrics_events = future_window[SETTINGS_DATA_SOURCE].filter(
        OPTION_COLUMN, lambda option: option == TARGET_OPTION
    )
    if not len(biometrics_events):
        return np.array([0], dtype=np.float32)

    enabled = biometrics_events[ACTION_COLUMN].events[-1] == TARGET_ACTION
    return np.array([int(enabled)], dtype=np.float32)

Step-by-Step Breakdown

① Check if already enabled

Python
past_biometrics = history[SETTINGS_DATA_SOURCE].filter(
    OPTION_COLUMN, lambda option: option == TARGET_OPTION
)
if len(past_biometrics) and past_biometrics[ACTION_COLUMN].events[-1] == TARGET_ACTION:
    return np.array([0], dtype=np.float32)

Before looking at the future, the function checks the user's history. If their most recent biometrics-related settings event already has the action "enable", they have already adopted the feature. These users are labeled as negative (0) rather than excluded — they represent successfully converted users who should not be targeted again.

② Compute month-end boundary

Python
month_end_ts = get_last_timestamp_of_month(split_ts)
if split_ts == month_end_ts:
    return None

The helper function get_last_timestamp_of_month calculates the last second of the month containing the split timestamp. This handles varying month lengths (28-31 days) correctly. If the split falls exactly on month-end, the entity is excluded since there is no remaining window to observe.

③ Filter future events to month boundary

Python
month_window = timedelta(seconds=month_end_ts - split_ts)
future_window = future.interval_from(split_ts, month_window)

A timedelta is computed from the split timestamp to the end of the month, and future events are trimmed to this window. This creates a calendar-aligned prediction target that matches business reporting periods.

④ Check for enablement action

Python
biometrics_events = future_window[SETTINGS_DATA_SOURCE].filter(
    OPTION_COLUMN, lambda option: option == TARGET_OPTION
)
if not len(biometrics_events):
    return np.array([0], dtype=np.float32)

enabled = biometrics_events[ACTION_COLUMN].events[-1] == TARGET_ACTION
return np.array([int(enabled)], dtype=np.float32)

The function filters for biometrics-related settings events in the future window. If none exist, the user did not change the setting — labeled negative. If events exist, the last action determines the label: if the final action is "enable", the label is positive (the user may have toggled on/off but ended up enabled).


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=biometric_login_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. Run predictions early in the month. Since the target window is "by month-end," scoring users in the first week of the month gives campaigns the most time to influence behavior.
  2. Handle enable/disable toggles carefully. Some users enable biometrics and then disable it. The target function uses the last action in the window, which is the correct approach — but monitor toggle frequency to identify UX friction.
  3. Consider device compatibility. Not all devices support biometric login. If device information is available as an attribute, exclude incompatible devices from training to avoid labeling users who physically cannot enable the feature.
  4. Account for timezone differences. The month-end calculation uses server timestamps. If your user base spans multiple time zones, consider whether "month-end" should be aligned to the user's local time or the server's time.
  5. Segment by user tenure. New users may be more receptive to biometric login prompts during onboarding, while long-tenured users who have not enabled it may need different messaging. Consider building separate models or adding tenure as a feature.