Skip to main content

Dual Model API

The dualModel router exposes the dual model architecture -- a platform-wide model and a customer-specific model that runs on tenant data. Compare predictions side by side, detect divergences, and configure customer model adjustments with guardrails.

Router namespace: dualModel

Source: src/server/trpc/routers/dual-model.ts

Procedures

ProcedureTypeAccessDescription
getDualScoresquerytenantPlatform and customer model scores for an entity type
getEntityDualScorequerytenantFull dual prediction for a single entity
getDivergenceAnalysisqueryadminSignificant divergences between models
getAdjustmentConfigqueryadminCustomer model adjustment configuration
updateAdjustmentsmutationadminUpdate adjustment config with guardrail validation
getModelComparisonqueryadminSide-by-side model comparison for one entity

getDualScores

Returns both platform and customer model scores for a set of entities. The blended score combines both according to the configured weights.

dualModel.getDualScores.queryOptions(input)

Input

FieldTypeRequiredDefaultDescription
entityType"person" | "account" | "opportunity"Yes--Entity type
limitnumber (1-500)No100Maximum entities to return

Response

[
{
"entityId": "acc_456",
"entityType": "account",
"platformScore": 78.2,
"customerScore": 84.5,
"blendedScore": 81.4,
"divergence": 6.3,
"tier": "warm"
}
]

Example

const trpc = useTRPC();
const { data } = useQuery(
trpc.dualModel.getDualScores.queryOptions({
entityType: "account",
limit: 200,
})
);

getEntityDualScore

Returns the full dual model prediction for a single entity, including both model scores, the blended result, and feature-level differences.

dualModel.getEntityDualScore.queryOptions(input)

Input

FieldTypeRequiredDescription
entityIdstringYesEntity identifier

Response

{
"entityId": "acc_456",
"platformScore": 78.2,
"customerScore": 84.5,
"blendedScore": 81.4,
"divergence": 6.3,
"platformTier": "warm",
"customerTier": "hot",
"blendedTier": "warm",
"featureDiffs": [
{ "feature": "industry_fit", "platform": 0.8, "customer": 0.95, "diff": 0.15 },
{ "feature": "engagement_recency", "platform": 0.6, "customer": 0.55, "diff": -0.05 }
]
}
Tier Disagreement

When the platform and customer models assign different tiers, the blended tier is computed from the blended score. Feature diffs help explain why the models disagree.


getDivergenceAnalysis

Returns entities where the platform and customer models significantly disagree, optionally filtered by divergence category.

dualModel.getDivergenceAnalysis.queryOptions(input)

Access: Admin only

Input

FieldTypeRequiredDefaultDescription
category"all" | "customer_higher" | "platform_higher"No"all"Filter by divergence direction
limitnumber (1-200)No50Maximum entities to return

Response

{
"totalDivergent": 23,
"avgDivergence": 12.4,
"entities": [
{
"entityId": "acc_789",
"entityType": "account",
"platformScore": 45.0,
"customerScore": 78.5,
"divergence": 33.5,
"category": "customer_higher",
"topDivergentFeatures": ["industry_fit", "company_size"]
}
]
}
tip

Large divergences often indicate that the customer model has learned tenant-specific patterns the platform model misses. Review these to validate whether the customer model is capturing real signal or overfitting.


getAdjustmentConfig

Returns the current customer model adjustment configuration including blend weights, score thresholds, and manual overrides.

dualModel.getAdjustmentConfig.queryOptions()

Access: Admin only

Input

None.

Response

{
"blendWeights": {
"platform": 0.6,
"customer": 0.4
},
"thresholds": {
"maxDivergence": 30,
"minCustomerAuc": 0.60
},
"overrides": [
{
"entityId": "acc_999",
"overrideType": "pin_tier",
"value": "hot",
"reason": "Strategic account"
}
]
}

updateAdjustments

Updates the customer model adjustment configuration. All changes are validated against guardrails to prevent misconfiguration.

dualModel.updateAdjustments.mutationOptions(options)

Access: Admin only

Input

FieldTypeRequiredDescription
configobjectYesAdjustment configuration
config.blendWeights{ platform: number, customer: number }NoMust sum to 1.0
config.thresholdsobjectNoDivergence and quality thresholds
config.overridesarrayNoManual entity-level overrides

Response

{ "success": true }
Guardrails
  • Blend weights must sum to 1.0
  • Customer weight cannot exceed 0.8 (platform always has at least 20% influence)
  • minCustomerAuc cannot be set below 0.55
  • maxDivergence cannot exceed 50

Validation errors return a BAD_REQUEST with details on which guardrail was violated.

Example

const trpc = useTRPC();

const mutation = useMutation(
trpc.dualModel.updateAdjustments.mutationOptions({
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: trpc.dualModel.getAdjustmentConfig.queryKey(),
});
},
})
);

mutation.mutate({
config: {
blendWeights: { platform: 0.5, customer: 0.5 },
},
});

getModelComparison

Returns a detailed side-by-side comparison of how the platform and customer models score a specific entity, including per-feature contributions.

dualModel.getModelComparison.queryOptions(input)

Access: Admin only

Input

FieldTypeRequiredDescription
entityIdstringYesEntity identifier

Response

{
"entityId": "acc_456",
"platform": {
"score": 78.2,
"tier": "warm",
"features": [
{ "name": "industry_fit", "value": 0.8, "weight": 0.25 },
{ "name": "employee_count", "value": 0.7, "weight": 0.15 }
]
},
"customer": {
"score": 84.5,
"tier": "hot",
"features": [
{ "name": "industry_fit", "value": 0.95, "weight": 0.30 },
{ "name": "employee_count", "value": 0.7, "weight": 0.10 }
]
},
"blended": {
"score": 81.4,
"tier": "warm"
}
}