Thirty-seven percent of bookings never carry a passenger. Today we build the dashboard that finds out why.
“Our completion rate is sitting around sixty percent and finance thinks we're leaving money on the table. Nobody can tell me where it's going. You have the data. I have a board meeting on Friday — give me somewhere to start.”
That is a realistic brief in the way that matters most: it names a symptom, not a question. Converting it into something answerable is the first job — before you open any tool.
| Answerable question | Shape of answer | Chart |
|---|---|---|
| How do bookings actually end? | Breakdown of one category | Donut |
| Which failures are most common? | Ranked list within a category | Horizontal bar |
| Does failure depend on time of day? | Two measures, ordered dimension | Column + line |
| Which products carry the value? | Ranked measure by category | Horizontal bar |
| Where does it happen? | Ranked list with a rate | Table |
5,657 of 15,000 never carried a passenger.
5,657 failures × ₹235.73 average completed fare.
What the operation actually earned in 2024.
₹13,33,539 assumes every failed booking would have completed at the average fare. It would not have.
Some customers rebooked within minutes and are already inside the 9,343 completed. Some trips would have been short and cheap.
Halve it and you have ₹6.7 lakh against ₹22 lakh realised. The problem survives the most hostile assumption you can make.
| Dimension | CSV → Excel | Atlas Charts |
|---|---|---|
| Freshness | Frozen at export | Queries live data on load |
| Nested fields | Flatten route.pickup by hand | Dot notation works natively |
| Size ceiling | ~1M rows, painful well before | Aggregates server-side |
| Reproducibility | Steps live in one person's memory | The chart is the spec |
| Exploring once | Genuinely better | More structured |
Richer visual grammar, modelling layer, enterprise governance. Needs a connector, an extract schedule, a licence per seat, and an owner for all three.
Included with the cluster. No connector, no extract, nested documents native. Weaker at blending many sources.
Total control, no licence — and you own every bug forever.
This is the feature people miss. Everything from Lecture 14 is available inside the data source:
[
{ $addFields: {
hour: { $toInt: { $substr: ["$booked_at", 11, 2] } },
failed: { $cond: [{ $eq: ["$status", "Completed"] }, 0, 1] }
} }
]
Two lines — and hour and failed now exist as fields for every chart built on this source.
booked_at is a string here, so we slice characters 11–12 rather than calling $hour.| Channel | Visual role | You already know it as |
|---|---|---|
| X / Category | What the bars are | $group._id |
| Y / Value | How tall each one is | $sum / $avg / $count |
| Series / Colour | Splits into sub-bars | A compound _id |
| Filter | Which documents count | $match, placed first |
| Sort & limit | Ranking, top-N | $sort then $limit |
mongosh.Source — applies everywhere, invisible.
Chart — that chart only.
Dashboard — interactive, viewer-controlled.
A chart titled “Average Booking Value” that quietly filters to status: "Completed" is not wrong — ₹235.73 is correct for completed rides. But a reader assuming all 15,000 has been misled.
Donut. Label: status. Arc: COUNT.
One of the few cases where a donut beats a bar — the question is “what share?”, not “which is bigger?”
| Completed | 9,343 | 62.29% |
| Cancelled by Driver | 2,698 | 17.99% |
| No Driver Found | 1,036 | 6.91% |
| Cancelled by Customer | 1,022 | 6.81% |
| Incomplete | 901 | 6.01% |
| Vehicle type | Revenue | Bookings | Avg fare | Completion |
|---|---|---|---|---|
| Premier Sedan | ₹5,36,855 | 1,954 | ₹442.22 | 62.13% |
| Go Sedan | ₹4,91,473 | 2,854 | ₹278.77 | 61.77% |
| Uber XL | ₹4,31,927 | 1,231 | ₹558.77 | 62.79% |
Go Sedan has the most bookings. Premier Sedan earns the most revenue. A failed Uber XL costs ₹558.77 against a Go Sedan's ₹278.77.
| Hour | Bookings | Failure rate |
|---|---|---|
| 18:00 — busiest hour | 1,373 | 37.14% |
| 19:00 | 1,246 | 35.96% |
| 09:00 | 1,172 | 36.18% |
| 08:00 | 1,076 | 36.71% |
| 17:00 | 1,062 | 39.92% |
Every instinct says throw resources at the 18:00 peak. But 17:00 — one hour earlier — fails more often. Supply is thinnest just before demand peaks: drivers have not yet repositioned.
| Driver cancellation reason | Count | Share |
|---|---|---|
| Customer related issue | 729 | 27.02% |
| More than permitted people in there | 666 | 24.68% |
| Personal & Car related issues | 652 | 24.17% |
| The customer was coughing/sick | 651 | 24.13% |
Four reasons, all between 24% and 27%. The temptation is to pick the top bar and call it the answer. A 27% leader among four near-equal options is not a finding — it is a flat distribution.
Nobody can rank slices 7 through 12. Use a sorted bar.
Perspective distorts exactly the lengths the reader is comparing. Never correct in this course.
A line from “Go Sedan” to “Uber XL” implies a progression that does not exist.
Legitimate for different units. Scaling until the lines intersect dramatically is rhetoric, and it gets caught.
Sort hourly failure by rate and the top is 03:00 at 43.48% — on 46 bookings. One more cancellation moves it two points. Fix: { $match: { bookings: { $gte: 1000 } } } after $group.
booking_value is missing on failed bookings, and $avg skips it silently. Filter to the population you mean, then name it in the title.
Completion by vehicle spans 61.77%–62.79%. Plot 61–63 and the bars look dramatic. Start at zero — the real finding is that it barely varies.
Add bdma.rides as a source, attach the $addFields pipeline, create Ride Operations — Failure Analysis, and build all four charts.
After each chart, verify the number against your Lecture 14 pipeline. The dataset is seeded — there is no “close enough”. A mismatch is a filter you forgot.
Title every chart with its question and population. Arrange for reading order: the status donut goes top-left, because it frames everything else.