Skip to content

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     High

Score Calculation

File: src/services/trust.service.ts

Algorithm

typescript
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.

typescript
let verificationBonus = 0;
if (user.verificationLevel === 'IDENTITY') {
  verificationBonus = 10;
} else if (user.verificationLevel === 'FULL') {
  verificationBonus = 20;
}
LevelBonusDescription
NONE+0No verification
IDENTITY+10ID verified
FULL+20ID + worth/value verified

2. Account Age Bonus

Older accounts demonstrate commitment and reduce likelihood of fraudulent behavior.

typescript
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
AgeBonus
0 months+0
1 month+1
2 months+2
......
10+ months+10 (max)

3. Report Penalty

Resolved reports against the user reduce trust.

typescript
const reportPenalty = -5 * user.reportedBy.length;
ReportsPenalty
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.

typescript
const totalMatches = user.sentInterests.length + user.receivedInterests.length;
const interactionBonus = Math.min(10, Math.floor(totalMatches / 2));
Accepted InterestsBonus
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.

typescript
export async function reportScreenshotDetected(userId: string): Promise<void> {
  await applyTrustPenalty(userId, 10, 'screenshot_detected');
}
EventPenalty
Screenshot detected-10

Factor Summary

FactorImpactMax
Base Score+5050
Verification (IDENTITY)+1010
Verification (FULL)+2020
Account Age+1/month10
Matches+1/2 matches10
Reports-5 eachN/A
Screenshots-10 eachN/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:

typescript
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 RangeTierBadgeColor
80-100HighHighly TrustedEmerald
60-79MediumTrustedBlue
40-59LowBuilding TrustAmber
0-39Very LowNew MemberGray

Trust Badge Component

File: app/src/components/profile/TrustBadge.tsx

Visual Representation

tsx
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

TierIconMeaning
High✓ ShieldCheckVerified and trusted
Medium/LowShieldStandard user
Very Low⚠ ShieldAlertNew or flagged

Penalty Application

Direct Penalty

Used for immediate trust reduction without recalculation.

typescript
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

EventPenaltyApplied By
Screenshot detected-10Socket handler
Report resolved (valid)-5 to -50Admin action
Blackmail report-20Automatic

Blackmail Report Handling

Blackmail reports receive special treatment due to severity:

typescript
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:

  1. Admin manually triggers recalculation
  2. After verification status changes
  3. Periodically via background job (not implemented)

Admin Recalculation

typescript
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-trust

Discovery Impact

Trust score affects profile visibility in discovery:

typescript
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 100

Problematic 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/:id

Response includes trustScore field.

Manual Update

PATCH /api/admin/users/:id
{
  "trustScore": 75
}

Recalculate

POST /api/admin/users/:id/recalculate-trust

Response:

json
{
  "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)

One chat. Everything done.