Zaptam Trust System
Deep dive into trust score calculation, tiers, badges, and penalty system.
Overview
The trust system provides a 0-100 score representing a user's reliability and safety. It influences discovery ranking and provides visual trust indicators to other users.
Trust Score: 0 ─────────────────────────────────────── 100
│ │
▼ ▼
Untrusted Highly Trusted
│ │ │ │
0 40 60 80 100
│ │ │ │
Very Low Low Medium HighScore Calculation
File: src/services/trust.service.ts
Algorithm
export async function calculateTrustScore(userId: string): Promise<number> {
const user = await prisma.user.findUnique({
where: { id: userId },
include: {
reportedBy: {
where: {
status: { in: ['RESOLVED'] },
},
},
sentInterests: {
where: { status: 'ACCEPTED' },
},
receivedInterests: {
where: { status: 'ACCEPTED' },
},
},
});
const factors = getTrustFactors(user);
const baseScore = 50;
const totalScore = Math.min(
100,
Math.max(
0,
baseScore +
factors.verificationBonus +
factors.ageBonus +
factors.reportPenalty +
factors.interactionBonus
)
);
return Math.round(totalScore);
}Formula
Trust Score = BASE + Verification + Age + Interactions - Penalties
Where:
BASE = 50 (starting score)
Clamped to range [0, 100]Trust Factors
1. Verification Bonus
Higher verification levels indicate more thoroughly vetted users.
let verificationBonus = 0;
if (user.verificationLevel === 'IDENTITY') {
verificationBonus = 10;
} else if (user.verificationLevel === 'FULL') {
verificationBonus = 20;
}| Level | Bonus | Description |
|---|---|---|
| NONE | +0 | No verification |
| IDENTITY | +10 | ID verified |
| FULL | +20 | ID + worth/value verified |
2. Account Age Bonus
Older accounts demonstrate commitment and reduce likelihood of fraudulent behavior.
const monthsOld = Math.floor(
(Date.now() - user.createdAt.getTime()) / (30 * 24 * 60 * 60 * 1000)
);
const ageBonus = Math.min(10, monthsOld); // +1 per month, max +10| Age | Bonus |
|---|---|
| 0 months | +0 |
| 1 month | +1 |
| 2 months | +2 |
| ... | ... |
| 10+ months | +10 (max) |
3. Report Penalty
Resolved reports against the user reduce trust.
const reportPenalty = -5 * user.reportedBy.length;| Reports | Penalty |
|---|---|
| 0 | -0 |
| 1 | -5 |
| 2 | -10 |
| 3 | -15 |
| ... | ... |
Note: Only counts reports with status
RESOLVED(not DISMISSED)
4. Interaction Bonus
Successful matches indicate positive community engagement.
const totalMatches = user.sentInterests.length + user.receivedInterests.length;
const interactionBonus = Math.min(10, Math.floor(totalMatches / 2));| Accepted Interests | Bonus |
|---|---|
| 0-1 | +0 |
| 2-3 | +1 |
| 4-5 | +2 |
| ... | ... |
| 20+ | +10 (max) |
Note: Counts both sent AND received accepted interests
5. Screenshot Penalty (Direct)
Screenshots are detected client-side and reported to the server.
export async function reportScreenshotDetected(userId: string): Promise<void> {
await applyTrustPenalty(userId, 10, 'screenshot_detected');
}| Event | Penalty |
|---|---|
| Screenshot detected | -10 |
Factor Summary
| Factor | Impact | Max |
|---|---|---|
| Base Score | +50 | 50 |
| Verification (IDENTITY) | +10 | 10 |
| Verification (FULL) | +20 | 20 |
| Account Age | +1/month | 10 |
| Matches | +1/2 matches | 10 |
| Reports | -5 each | N/A |
| Screenshots | -10 each | N/A |
Score Ranges
Maximum Possible: 50 + 20 + 10 + 10 = 90 (without penalty deductions) Minimum Possible: 0 (many reports/screenshots)
Trust Tiers
Based on the calculated score, users fall into tiers:
const getScoreLevel = () => {
if (score >= 80) return { level: "high", label: "Highly Trusted", color: "text-emerald-500" };
if (score >= 60) return { level: "medium", label: "Trusted", color: "text-blue-500" };
if (score >= 40) return { level: "low", label: "Building Trust", color: "text-amber-500" };
return { level: "very-low", label: "New Member", color: "text-gray-500" };
};| Score Range | Tier | Badge | Color |
|---|---|---|---|
| 80-100 | High | Highly Trusted | Emerald |
| 60-79 | Medium | Trusted | Blue |
| 40-59 | Low | Building Trust | Amber |
| 0-39 | Very Low | New Member | Gray |
Trust Badge Component
File: app/src/components/profile/TrustBadge.tsx
Visual Representation
const Icon = level === "high" ? ShieldCheck :
level === "very-low" ? ShieldAlert : Shield;
return (
<div className={cn("flex items-center", color)}>
<Icon className="h-4 w-4" />
<span className="font-medium">{score}%</span>
</div>
);Badge Icons
| Tier | Icon | Meaning |
|---|---|---|
| High | ✓ ShieldCheck | Verified and trusted |
| Medium/Low | Shield | Standard user |
| Very Low | ⚠ ShieldAlert | New or flagged |
Penalty Application
Direct Penalty
Used for immediate trust reduction without recalculation.
export async function applyTrustPenalty(
userId: string,
penalty: number,
reason: string
): Promise<number> {
const user = await prisma.user.findUnique({
where: { id: userId },
});
if (!user) {
throw new NotFoundError('User not found');
}
const newScore = Math.max(0, user.trustScore - penalty);
await prisma.user.update({
where: { id: userId },
data: { trustScore: newScore },
});
console.log(`Trust penalty applied to ${userId}: -${penalty} for ${reason}`);
return newScore;
}Penalty Events
| Event | Penalty | Applied By |
|---|---|---|
| Screenshot detected | -10 | Socket handler |
| Report resolved (valid) | -5 to -50 | Admin action |
| Blackmail report | -20 | Automatic |
Blackmail Report Handling
Blackmail reports receive special treatment due to severity:
export async function createBlackmailReport(
reporterId: string,
reportedId: string,
description: string,
evidence?: any
): Promise<Report> {
const report = await prisma.report.create({
data: {
reporterId,
reportedId,
type: 'BLACKMAIL',
description,
evidence: evidence || null,
status: 'INVESTIGATING', // Skip PENDING
},
});
// IMMEDIATE -20 penalty
await prisma.user.update({
where: { id: reportedId },
data: {
trustScore: {
decrement: 20,
},
},
});
return report;
}Score Recalculation
When Triggered
Trust scores are recalculated:
- Admin manually triggers recalculation
- After verification status changes
- Periodically via background job (not implemented)
Admin Recalculation
export async function updateTrustScore(userId: string): Promise<number> {
const newScore = await calculateTrustScore(userId);
await prisma.user.update({
where: { id: userId },
data: { trustScore: newScore },
});
return newScore;
}Admin Endpoint:
POST /api/admin/users/:id/recalculate-trustDiscovery Impact
Trust score affects profile visibility in discovery:
const profiles = await prisma.user.findMany({
where: whereClause,
orderBy: [
{ verificationLevel: 'desc' }, // 1st priority
{ trustScore: 'desc' }, // 2nd priority
{ lastActiveAt: 'desc' }, // 3rd priority
],
...
});Higher trust = Higher visibility:
- Users with high trust scores appear earlier in discovery
- Combined with verification level for overall ranking
Score Examples
New User (Unverified)
Base: 50
Verification: 0 (NONE)
Age: 0 (new account)
Matches: 0 (none yet)
Penalties: 0 (clean record)
─────────────────
TOTAL: 50 (Building Trust)Established Verified User
Base: 50
Verification: +20 (FULL)
Age: +10 (1 year+)
Matches: +10 (20+ matches)
Penalties: 0 (clean record)
─────────────────
TOTAL: 90 (Highly Trusted) - capped at 100Problematic User
Base: 50
Verification: +10 (IDENTITY)
Age: +5 (5 months)
Matches: +5 (10 matches)
Penalties: -20 (4 resolved reports)
─────────────────
TOTAL: 50 (Building Trust)Severely Penalized User
Base: 50
Verification: +10 (IDENTITY)
Age: +3 (3 months)
Matches: +2 (4 matches)
Penalties: -50 (blackmail + reports + screenshots)
─────────────────
TOTAL: 15 (New Member)Admin Controls
View Trust Score
GET /api/admin/users/:idResponse includes trustScore field.
Manual Update
PATCH /api/admin/users/:id
{
"trustScore": 75
}Recalculate
POST /api/admin/users/:id/recalculate-trustResponse:
{
"success": true,
"data": {
"trustScore": 72
}
}Apply Penalty via Report
PATCH /api/admin/reports/:id
{
"status": "RESOLVED",
"applyPenalty": true,
"penaltyAmount": 15
}Future Improvements
Potential Additional Factors
- Response rate: Reward users who reply to messages
- Profile completeness: Bonus for filled-out profiles
- Photo quality: AI-assessed photo quality
- Activity consistency: Regular engagement patterns
- Positive feedback: Let users rate interactions
Decay Over Time
Consider:
- Slowly restoring trust after periods without incidents
- Penalties that decrease over time
- "Probation" periods after violations
Verification Weight
Consider increasing verification bonus:
- Full verification is the gold standard
- Make it worth even more (+30 or +40)