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
| Procedure | Type | Access | Description |
|---|---|---|---|
getDualScores | query | tenant | Platform and customer model scores for an entity type |
getEntityDualScore | query | tenant | Full dual prediction for a single entity |
getDivergenceAnalysis | query | admin | Significant divergences between models |
getAdjustmentConfig | query | admin | Customer model adjustment configuration |
updateAdjustments | mutation | admin | Update adjustment config with guardrail validation |
getModelComparison | query | admin | Side-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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
entityType | "person" | "account" | "opportunity" | Yes | -- | Entity type |
limit | number (1-500) | No | 100 | Maximum 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
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | Yes | Entity 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 }
]
}
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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
category | "all" | "customer_higher" | "platform_higher" | No | "all" | Filter by divergence direction |
limit | number (1-200) | No | 50 | Maximum 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"]
}
]
}
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
| Field | Type | Required | Description |
|---|---|---|---|
config | object | Yes | Adjustment configuration |
config.blendWeights | { platform: number, customer: number } | No | Must sum to 1.0 |
config.thresholds | object | No | Divergence and quality thresholds |
config.overrides | array | No | Manual entity-level overrides |
Response
{ "success": true }
- Blend weights must sum to 1.0
- Customer weight cannot exceed 0.8 (platform always has at least 20% influence)
minCustomerAuccannot be set below 0.55maxDivergencecannot 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
| Field | Type | Required | Description |
|---|---|---|---|
entityId | string | Yes | Entity 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"
}
}
Related Pages
- Propensity API -- ML propensity predictions feeding both models
- Scoring Config API -- Base scoring configuration
- Analytics API -- Score analytics and conversion attribution