Bugnik
← All problems

34Driver Ratings API

Node.js · ExpressEasyBug fixSuggested time: 60 min

Driver ratings REST API (14-day window, rounded averages, badges, hidden ratings, leaderboard)

New to repository-based OA questions? Read the guide.

Sign in to start

Before you start

You'll get more out of this problem if you're comfortable with:

Language

  • CommonJS modules
  • Averages over a list of numbers

Libraries and frameworks

  • Express 5 routers and handlers
  • Mongoose schemas and queries

Build and test tools

  • Reading Node.js startup errors and Jest + Supertest failures

Reasoning

  • Driver rating summaries
  • Time windows measured in days

Background

The Last Mile team at a large online retailer lets customers rate the driver who delivered their order. Operations uses the scores to reward great drivers and to coach drivers who are struggling. The service is a Node.js REST API (Express + MongoDB via Mongoose). After a recent clean-up the service stopped starting, and the scores that do come out look off.

How the system works

Callers

  • Every request needs an x-user-id header (already authenticated upstream). A missing or blank header is 401 with { "error": "sign in required" }.
  • The dispatch system also sends x-role: dispatcher; operations staff send x-role: ops (roles are compared case-insensitively).

Errors

Every error response is { "error": "<message>" }: 400 invalid input, 401 no x-user-id, 403 not allowed, 404 unknown delivery, 409 a rule was broken (duplicate delivery, outside the rating window).

Deliveries

POST /deliveries (dispatchers only, else 403): body { "deliveryId", "driverId", "customerId", "deliveredAt" }.

  • deliveryId is trimmed and upper-cased and must look like D- followed by 4–10 digits.
  • driverId and customerId are trimmed, 1–40 characters.
  • deliveredAt is an ISO 8601 timestamp with a time zone (Z or +05:30), e.g. 2026-07-01T09:30:00Z. It is returned in UTC.
  • A deliveryId that is already recorded is 409. Returns 201 with { "deliveryId", "driverId", "customerId", "deliveredAt" }.

Ratings

PUT /deliveries/:deliveryId/rating: body { "stars", "comment", "tags" }.

  • Only the delivery's customerId may rate it (else 403); an unknown delivery is 404.
  • stars is a whole number from 1 to 5. comment is optional text (trimmed, at most 280 characters, default ""). tags is an optional list of ON_TIME, FRIENDLY, CAREFUL, LATE, DAMAGED (upper-cased; repeats dropped, keeping the first position; anything else is 400).
  • Ratings are accepted from the moment of delivery up to and including 14 days (14 × 24 hours) after deliveredAt; before or after that it's 409.
  • A delivery has at most one rating. The first rating returns 201; rating again replaces stars, comment and tags (missing fields go back to their defaults) and returns 200.
  • A rating that operations has hidden can't be changed (409).
  • The response is { "deliveryId", "driverId", "stars", "comment", "tags", "ratedAt", "hidden" }, where ratedAt is when the rating was last set.

PUT /deliveries/:deliveryId/rating/visibility (operations only, else 403): body { "hidden", "reason" }.

  • hidden must be true or false. Hiding needs a reason (trimmed, 5–200 characters); showing again doesn't. A delivery without a rating is 404. Returns 200 with the rating.
  • Hidden ratings don't count anywhere: not in scores, lists, the last-7-days figures or the leaderboard.

Driver score

GET /drivers/:driverId/score: 200 with { "driverId", "ratings", "average", "lowRatings", "badge", "last7Days" }.

  • ratings: how many ratings the driver has.
  • average: the mean of the stars rounded to one decimal place, halves rounded up (4.65 → 4.7, 4.333… → 4.3), or null with no ratings.
  • lowRatings: ratings of 1 or 2 stars.
  • badge (decided from the rounded average, and only with at least 5 ratings; otherwise null): "TOP" when the average is 4.7 or more, "WATCH" when it is below 3.5, else null.
  • last7Days: { "ratings", "average" } computed the same way from the ratings whose ratedAt is at most 7 days (7 × 24 hours, inclusive) before now.

GET /drivers/:driverId/ratings?limit=N: 200 with { "ratings": [...] }, the driver's most recent ratings, newest ratedAt first; ratings with the same ratedAt are ordered by deliveryId (A to Z). limit is optional (default 10) and must be a whole number from 1 to 50 (else 400).

GET /drivers/leaderboard?minRatings=N: 200 with { "drivers": [ { "driverId", "ratings", "average" } ] }: at most 10 drivers that have at least minRatings ratings (optional, default 5, a whole number from 1 to 1000), ordered by the rounded average (highest first), then by more ratings, then by driverId (A to Z).

Project structure

src/
  app.js                         createApp(): JSON parsing, routes, error handler
  server.js                      connects to MongoDB and starts the server
  routes/
    deliveries.js                /deliveries
    drivers.js                   /drivers
  controllers/
    deliveryController.js        record a delivery, rate it, hide a rating
    driverController.js          driver score, recent ratings, leaderboard
  middleware/
    auth.js                      x-user-id and role checks
    errorHandler.js              errors → status + { error }
  models/
    Delivery.js                  a completed delivery
    Rating.js                    a customer's rating of a delivery
  services/
    deliveryService.js           delivery lookups
    ratingService.js             rating rules and recent ratings
    scoreService.js              average, low ratings, badges, leaderboard
  utils/
    AppError.js                  an error with an HTTP status
    input.js                     reading ids, text, timestamps, tags and query numbers
    present.js                   documents → API JSON
    tags.js                      the allowed rating tags
tests/
  score.test.js                  average and badge rules
  ratings.test.js                delivery and rating endpoints
  drivers.test.js                driver endpoints
  setup/                         in-memory MongoDB for the tests

Your task

Operations and drivers report these problems:

  • The service doesn't start.
  • Some drivers see a lower average than their ratings give, and miss a badge they earned.
  • A driver's ratings page shows their oldest ratings instead of the latest ones.

Find and fix the defects so that all tests pass.

  • Do not modify tests/, package.json or jest.config.js.
  • Keep your changes minimal. Fix the defects; don't rewrite modules.
  • Additional hidden tests check the behaviour described in this README.