Backend reputation metrics are pre-aggregated in Postgres (freelancer_reputation) and refreshed when a snapshot is missing or older than five minutes (configurable in code via REPUTATION_SNAPSHOT_MAX_AGE_MS). This keeps reads cheap at scale while staying close to real-time job data.
| Field | Meaning |
|---|---|
| completionRate | jobsCompleted / jobsStarted, capped 0–1. jobsStarted counts all jobs where the user is freelancer_id (any non-null assignment). |
| disputeRate | Share of those jobs that either have status = 'disputed' or at least one row in disputes for that job_id. |
| totalVolume | Sum of budget over jobs with status = 'completed'. Amounts follow whatever currency is on each job; if you mix currencies, treat this as informational or extend the API to group by currency. |
| onTimeDeliveryPct | Among completed jobs with both deadline and completed_at, the fraction where completed_at <= deadline. If none qualify, this is null. |
| reputationScore | Optional display score 0–100: 0.4 * completion + 0.35 * onTime + 0.25 * (1 - dispute), with 0.5 used for on-time when there is no on-time sample but the user has started jobs. null when jobsStarted === 0. |
Rates and the score are null when denominators are zero (except totalVolume, which is 0).
- Path:
userIdis the integer primary key fromusers.id. - Auth: none.
- Cache:
Cache-Control: public, s-maxage=60, stale-while-revalidate=300. - Errors:
400invalid id,404user missing,503database failure.
Example response
{
"userId": 42,
"metrics": {
"completionRate": 0.92,
"disputeRate": 0.04,
"totalVolume": "12500.00",
"onTimeDeliveryPct": 0.88,
"jobsStarted": 25,
"jobsCompleted": 23,
"jobsWithDispute": 1,
"completedWithDeadline": 20,
"onTimeDeliveries": 17
},
"reputationScore": 86.7,
"computedAt": "2026-03-24T12:00:00.000Z"
}- Auth: session / access cookie (same as
/api/auth/me). - Resolution:
users.wallet_addressmust match the token’s wallet; returns that row’s reputation. - Query:
?refresh=1or?refresh=trueforces a recomputation before responding (use sparingly). - Cache:
Cache-Control: private, no-store. - Errors:
401unauthenticated,404nousersrow for wallet,503database failure.
Run the migration after 001-create-tables.sql / 002-auth-tables.sql:
# Example: pipe into psql or Neon's SQL editor
scripts/003-freelancer-reputation.sqlThis adds jobs.completed_at, table freelancer_reputation, and indexes on (freelancer_id, status) and completed jobs to keep aggregations fast as data grows.
- Profile pages: call the public route with the profile’s numeric
userId. - Dashboard “my reputation”: call
/api/freelancer/reputationwithcredentials: 'include'(see existing dashboard fetch patterns inlib/freelancer-dashboard.ts).
After job completion flows (including the Stellar worker), ensure completed_at is set so on-time delivery stays accurate; the worker updates it when marking a job completed from escrow release.