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 casenp.array([0], dtype=np.float32)— negative caseNone— exclude this entity from training
Full Example
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
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
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
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
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:
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
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
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)
Recommended Metrics
| 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
- 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.
- 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.
- 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.
- 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.
- 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.