Skip to main content
Lenders and insurers rely on black-box ML models (gradient boosted trees, neural networks, transformers) to score applicant risk. Pairing each decision with specific reasoning gives underwriters the traceability they need for policy optimization, accelerated underwriting, and compliant audit. OuterProduct bolts onto your existing scoring model without replacing it, adding a reasoning engine that runs alongside the scorer in real time and leaves the production decision path untouched. This guide walks you through wrapping your existing scoring model in a reasoning app with intelligence at three levels:
  • Scoring model: what the model does in aggregate, how it aligns with business rules and KPIs, and its data efficiency for feature sourcing and model improvement.
  • Patterns and trends: fingerprinting applicant cohorts and tracking how risk patterns emerge and evolve.
  • Cases: for one applicant, why the decision landed where it did and what would flip it.
1

Distill your scoring model

Point at historical applicant data via a connector, wrap your scoring endpoint as a Predictor, and pass it as teacher to op.reasoning.fit. OuterProduct trains a reasoning engine alongside your scorer and leaves the production decision path untouched.
import outerproduct as op

op.init()

# Historical applicants (for training) — already in Databricks. Replace
# the angle-bracket placeholders below with your workspace's values;
# `connector_credential_name` references a Databricks credential you've already
# added in console.outerproduct.com.
connector = op.DatabricksConnector(
    server_hostname="<workspace>.cloud.databricks.com",
    http_path="/sql/1.0/warehouses/<warehouse-id>",
    catalog="<catalog>",
    schema_name="<schema>",
    connector_credential_name="underwriting-prod",
)
dataset = connector.table("applicants_train")

# Your production scorer — typically a Databricks Model Serving endpoint.
teacher = op.model.Predictor(
    "https://underwriting.example.com/score",
    headers={"Authorization": "Bearer ..."},
)

reasoning_model = op.reasoning.fit(dataset, teacher=teacher).wait()
See the Connectors guide for Databricks, Snowflake, and S3 sources, and the Training guide for full distillation details.
2

Plug in the daily applicant stream

Lenders see a constant stream of applications, each scored on arrival. Pull the day’s batch from your warehouse using the same feature schema as training and pass it through the reasoning engine. Predictions come back paired in real time with the rich context that powers every level of the app.
import pandas as pd
from databricks import sql

with sql.connect(**databricks_config) as cnx:
    applicants_today = pd.read_sql(
        "SELECT * FROM underwriting.applicants_today",
        cnx,
    )

today = op.LocalDataset.from_pandas(applicants_today).upload()
predictions, reasoning = reasoning_model.predict_and_explain(today)
3

Reason at three altitudes

Use the predictions and reasoning context to power every level of your underwriting app - from portfolio-wide KPIs down to a single applicant’s audit trail.

Scoring engine - KPIs, drivers, and drift

Track headline KPIs (e.g., denials) over time, watch for anomalies and drift, and characterize the features driving risk across the population. Because get_global_drivers() exposes the features the model relies on, the view doubles as a data-efficiency check - features with low utilization are candidates for retirement.
importance = reasoning_model.get_global_drivers()
for name, score in zip(importance.feature_names, importance.attributions):
    print(f"{name}: {score:.4f}")

Applicant patterns - recurring denial profiles

Fit a PatternTracker on the denial band to surface the dominant combinations of features that drive risk decisions. Each pattern is an executable filter with precision and lift stats, ready to apply to fresh applicants.
pt = op.reasoning.pattern_tracker.fit(
    reasoning_model, dataset, target_range=(0.5, None)
).wait()
for fp in pt.patterns:
    print(f"{fp.label}: precision={fp.precision:.2f} lift={fp.lift:.2f}")

# Apply to today's batch: which patterns each new applicant matches.
matches = pt.transform(today)
distribution = pt.distribution(today)
See the Pattern Tracker guide for the full API including partition and labeling options.

Individual applications - explanation and scenario

For one applicant, pair the explanation with a counterfactual scenario using scenario(). The explanation serves as the audit trail on file. The scenario doubles as the defensibility argument when a decision is challenged, and as a forward-looking recommendation. For example, “if the applicant resolves one recent credit inquiry and verifies stable employment, they will be approved on reapplication.”
applicant = op.LocalDataset.from_pandas(applicants_today.iloc[[42]]).upload()

prediction, explanation = reasoning_model.predict_and_explain(applicant)
result = reasoning_model.scenario(applicant, target_class=1, max_steps=10)

for cf in result.queries[0].counterfactuals:
    print(f"If the applicant changes {cf.n_changes} factor(s), they're approved:")
    for feature, change in cf.changes.items():
        print(f"  {feature}: {change.before}{change.after}")

A composable reasoning layer

get_global_drivers(), pattern_tracker.fit(), predict_and_explain(), and scenario() compose freely in a custom app. Plug them into your enterprise warehouses, BI tools, rule engines, and case-management systems, and surface reasoning wherever it’s needed: an underwriter dashboard, a portfolio alert, a write-back into the case UI, or an LLM agent drafting case notes.
Enrich the reasoning context further with signals from your wider enterprise data, complementary models, and underwriting policy rules to get the most out of each level.

Where to go next

Explanations

Feature attributions and rule-based explanations for every prediction.

Counterfactuals

Control scenario search and read the returned ScenarioResult.